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