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
18package com.android.mms.ui;
19
20import android.app.Activity;
21import android.app.AlertDialog;
22import android.content.ContentResolver;
23import android.content.ContentUris;
24import android.content.ContentValues;
25import android.content.DialogInterface;
26import android.content.DialogInterface.OnClickListener;
27import android.content.Intent;
28import android.database.Cursor;
29import android.database.sqlite.SqliteWrapper;
30import android.net.Uri;
31import android.os.Bundle;
32import android.os.Handler;
33import android.os.Message;
34import android.os.SystemClock;
35import android.provider.Telephony.Sms;
36import android.provider.Telephony.Sms.Inbox;
37import android.telephony.SmsMessage;
38import android.text.TextUtils;
39import android.util.Log;
40import android.view.Window;
41
42import com.android.mms.LogTag;
43import com.android.mms.R;
44import com.android.mms.transaction.MessagingNotification;
45
46import java.util.ArrayList;
47
48/**
49 * Display a class-zero SMS message to the user. Wait for the user to dismiss
50 * it.
51 */
52public 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
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
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
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
129    protected void onNewIntent(Intent msgIntent) {
130        /* Running with another visible message, queue this one */
131        queueMsgFromIntent(msgIntent);
132    }
133
134    @Override
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
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
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
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
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
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
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
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