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