1 /* 2 * Copyright (C) 2007-2008 Esmertec AG. 3 * Copyright (C) 2007-2008 The Android Open Source Project 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.mms.ui; 19 20 import android.app.Activity; 21 import android.app.AlertDialog; 22 import android.content.ContentResolver; 23 import android.content.ContentUris; 24 import android.content.ContentValues; 25 import android.content.DialogInterface; 26 import android.content.DialogInterface.OnClickListener; 27 import android.content.Intent; 28 import android.database.Cursor; 29 import android.database.sqlite.SqliteWrapper; 30 import android.net.Uri; 31 import android.os.Bundle; 32 import android.os.Handler; 33 import android.os.Message; 34 import android.os.SystemClock; 35 import android.provider.Telephony.Sms; 36 import android.provider.Telephony.Sms.Inbox; 37 import android.telephony.SmsMessage; 38 import android.text.TextUtils; 39 import android.util.Log; 40 import android.view.Window; 41 42 import com.android.mms.LogTag; 43 import com.android.mms.R; 44 import com.android.mms.transaction.MessagingNotification; 45 46 import java.util.ArrayList; 47 48 /** 49 * Display a class-zero SMS message to the user. Wait for the user to dismiss 50 * it. 51 */ 52 public class ClassZeroActivity extends Activity { 53 private static final String TAG = LogTag.TAG; 54 private static final int ON_AUTO_SAVE = 1; 55 private static final String[] REPLACE_PROJECTION = new String[] { Sms._ID, 56 Sms.ADDRESS, Sms.PROTOCOL }; 57 private static final int REPLACE_COLUMN_ID = 0; 58 59 /** Default timer to dismiss the dialog. */ 60 private static final long DEFAULT_TIMER = 5 * 60 * 1000; 61 62 /** To remember the exact time when the timer should fire. */ 63 private static final String TIMER_FIRE = "timer_fire"; 64 65 private SmsMessage mMessage = null; 66 67 /** Is the message read. */ 68 private boolean mRead = false; 69 70 /** The timer to dismiss the dialog automatically. */ 71 private long mTimerSet = 0; 72 private AlertDialog mDialog = null; 73 74 private ArrayList<SmsMessage> mMessageQueue = null; 75 76 private Handler mHandler = new Handler() { 77 @Override 78 public void handleMessage(Message msg) { 79 // Do not handle an invalid message. 80 if (msg.what == ON_AUTO_SAVE) { 81 mRead = false; 82 mDialog.dismiss(); 83 saveMessage(); 84 processNextMessage(); 85 } 86 } 87 }; 88 queueMsgFromIntent(Intent msgIntent)89 private boolean queueMsgFromIntent(Intent msgIntent) { 90 byte[] pdu = msgIntent.getByteArrayExtra("pdu"); 91 String format = msgIntent.getStringExtra("format"); 92 SmsMessage rawMessage = SmsMessage.createFromPdu(pdu, format); 93 String message = rawMessage.getMessageBody(); 94 if (TextUtils.isEmpty(message)) { 95 if (mMessageQueue.size() == 0) { 96 finish(); 97 } 98 return false; 99 } 100 mMessageQueue.add(rawMessage); 101 return true; 102 } 103 processNextMessage()104 private void processNextMessage() { 105 mMessageQueue.remove(0); 106 if (mMessageQueue.size() == 0) { 107 finish(); 108 } else { 109 displayZeroMessage(mMessageQueue.get(0)); 110 } 111 } 112 saveMessage()113 private void saveMessage() { 114 Uri messageUri = null; 115 if (mMessage.isReplace()) { 116 messageUri = replaceMessage(mMessage); 117 } else { 118 messageUri = storeMessage(mMessage); 119 } 120 if (!mRead && messageUri != null) { 121 MessagingNotification.nonBlockingUpdateNewMessageIndicator( 122 this, 123 MessagingNotification.THREAD_ALL, // always notify on class-zero msgs 124 false); 125 } 126 } 127 128 @Override onNewIntent(Intent msgIntent)129 protected void onNewIntent(Intent msgIntent) { 130 /* Running with another visible message, queue this one */ 131 queueMsgFromIntent(msgIntent); 132 } 133 134 @Override onCreate(Bundle icicle)135 protected void onCreate(Bundle icicle) { 136 super.onCreate(icicle); 137 requestWindowFeature(Window.FEATURE_NO_TITLE); 138 getWindow().setBackgroundDrawableResource( 139 R.drawable.class_zero_background); 140 141 if (mMessageQueue == null) { 142 mMessageQueue = new ArrayList<SmsMessage>(); 143 } 144 145 if (!queueMsgFromIntent(getIntent())) { 146 return; 147 } 148 149 if (mMessageQueue.size() == 1) { 150 displayZeroMessage(mMessageQueue.get(0)); 151 } 152 153 if (icicle != null) { 154 mTimerSet = icicle.getLong(TIMER_FIRE, mTimerSet); 155 } 156 } 157 displayZeroMessage(SmsMessage rawMessage)158 private void displayZeroMessage(SmsMessage rawMessage) { 159 String message = rawMessage.getMessageBody(); 160 /* This'll be used by the save action */ 161 mMessage = rawMessage; 162 163 mDialog = new AlertDialog.Builder(this, AlertDialog.THEME_HOLO_DARK).setMessage(message) 164 .setPositiveButton(R.string.save, mSaveListener) 165 .setNegativeButton(android.R.string.cancel, mCancelListener) 166 .setCancelable(false).show(); 167 long now = SystemClock.uptimeMillis(); 168 mTimerSet = now + DEFAULT_TIMER; 169 } 170 171 @Override onStart()172 protected void onStart() { 173 super.onStart(); 174 long now = SystemClock.uptimeMillis(); 175 if (mTimerSet <= now) { 176 // Save the message if the timer already expired. 177 mHandler.sendEmptyMessage(ON_AUTO_SAVE); 178 } else { 179 mHandler.sendEmptyMessageAtTime(ON_AUTO_SAVE, mTimerSet); 180 if (false) { 181 Log.d(TAG, "onRestart time = " + Long.toString(mTimerSet) + " " 182 + this.toString()); 183 } 184 } 185 } 186 187 @Override onSaveInstanceState(Bundle outState)188 protected void onSaveInstanceState(Bundle outState) { 189 super.onSaveInstanceState(outState); 190 outState.putLong(TIMER_FIRE, mTimerSet); 191 if (false) { 192 Log.d(TAG, "onSaveInstanceState time = " + Long.toString(mTimerSet) 193 + " " + this.toString()); 194 } 195 } 196 197 @Override onStop()198 protected void onStop() { 199 super.onStop(); 200 mHandler.removeMessages(ON_AUTO_SAVE); 201 if (false) { 202 Log.d(TAG, "onStop time = " + Long.toString(mTimerSet) 203 + " " + this.toString()); 204 } 205 } 206 207 private final OnClickListener mCancelListener = new OnClickListener() { 208 public void onClick(DialogInterface dialog, int whichButton) { 209 dialog.dismiss(); 210 processNextMessage(); 211 } 212 }; 213 214 private final OnClickListener mSaveListener = new OnClickListener() { 215 public void onClick(DialogInterface dialog, int whichButton) { 216 mRead = true; 217 saveMessage(); 218 dialog.dismiss(); 219 processNextMessage(); 220 } 221 }; 222 extractContentValues(SmsMessage sms)223 private ContentValues extractContentValues(SmsMessage sms) { 224 // Store the message in the content provider. 225 ContentValues values = new ContentValues(); 226 227 values.put(Inbox.ADDRESS, sms.getDisplayOriginatingAddress()); 228 229 // Use now for the timestamp to avoid confusion with clock 230 // drift between the handset and the SMSC. 231 values.put(Inbox.DATE, new Long(System.currentTimeMillis())); 232 values.put(Inbox.PROTOCOL, sms.getProtocolIdentifier()); 233 values.put(Inbox.READ, Integer.valueOf(mRead ? 1 : 0)); 234 values.put(Inbox.SEEN, Integer.valueOf(mRead ? 1 : 0)); 235 236 if (sms.getPseudoSubject().length() > 0) { 237 values.put(Inbox.SUBJECT, sms.getPseudoSubject()); 238 } 239 values.put(Inbox.REPLY_PATH_PRESENT, sms.isReplyPathPresent() ? 1 : 0); 240 values.put(Inbox.SERVICE_CENTER, sms.getServiceCenterAddress()); 241 return values; 242 } 243 replaceMessage(SmsMessage sms)244 private Uri replaceMessage(SmsMessage sms) { 245 ContentValues values = extractContentValues(sms); 246 247 values.put(Inbox.BODY, sms.getMessageBody()); 248 249 ContentResolver resolver = getContentResolver(); 250 String originatingAddress = sms.getOriginatingAddress(); 251 int protocolIdentifier = sms.getProtocolIdentifier(); 252 String selection = Sms.ADDRESS + " = ? AND " + Sms.PROTOCOL + " = ?"; 253 String[] selectionArgs = new String[] { originatingAddress, 254 Integer.toString(protocolIdentifier) }; 255 256 Cursor cursor = SqliteWrapper.query(this, resolver, Inbox.CONTENT_URI, 257 REPLACE_PROJECTION, selection, selectionArgs, null); 258 259 try { 260 if (cursor.moveToFirst()) { 261 long messageId = cursor.getLong(REPLACE_COLUMN_ID); 262 Uri messageUri = ContentUris.withAppendedId( 263 Sms.CONTENT_URI, messageId); 264 265 SqliteWrapper.update(this, resolver, messageUri, values, 266 null, null); 267 return messageUri; 268 } 269 } finally { 270 cursor.close(); 271 } 272 return storeMessage(sms); 273 } 274 storeMessage(SmsMessage sms)275 private Uri storeMessage(SmsMessage sms) { 276 // Store the message in the content provider. 277 ContentValues values = extractContentValues(sms); 278 values.put(Inbox.BODY, sms.getDisplayMessageBody()); 279 ContentResolver resolver = getContentResolver(); 280 if (false) { 281 Log.d(TAG, "storeMessage " + this.toString()); 282 } 283 return SqliteWrapper.insert(this, resolver, Inbox.CONTENT_URI, values); 284 } 285 } 286