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