1/*
2 * Copyright (C) 2008 Esmertec AG.
3 * Copyright (C) 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.util;
19
20import android.content.BroadcastReceiver;
21import android.content.ContentValues;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.content.SharedPreferences;
26import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
27import android.database.Cursor;
28import android.database.sqlite.SqliteWrapper;
29import android.net.Uri;
30import android.os.Handler;
31import android.os.SystemProperties;
32import android.preference.PreferenceManager;
33import android.provider.Telephony.Mms;
34import android.telephony.ServiceState;
35import android.util.Log;
36import android.widget.Toast;
37
38import com.android.internal.telephony.TelephonyIntents;
39import com.android.internal.telephony.TelephonyProperties;
40import com.android.mms.R;
41import com.android.mms.data.Contact;
42import com.android.mms.ui.MessagingPreferenceActivity;
43import com.google.android.mms.MmsException;
44import com.google.android.mms.pdu.EncodedStringValue;
45import com.google.android.mms.pdu.NotificationInd;
46import com.google.android.mms.pdu.PduPersister;
47
48public class DownloadManager {
49    private static final String TAG = "DownloadManager";
50    private static final boolean DEBUG = false;
51    private static final boolean LOCAL_LOGV = false;
52
53    public static final int DEFERRED_MASK           = 0x04;
54
55    public static final int STATE_UNKNOWN           = 0x00;
56    public static final int STATE_UNSTARTED         = 0x80;
57    public static final int STATE_DOWNLOADING       = 0x81;
58    public static final int STATE_TRANSIENT_FAILURE = 0x82;
59    public static final int STATE_PERMANENT_FAILURE = 0x87;
60    public static final int STATE_PRE_DOWNLOADING   = 0x88;
61
62    private final Context mContext;
63    private final Handler mHandler;
64    private final SharedPreferences mPreferences;
65    private boolean mAutoDownload;
66
67    private final OnSharedPreferenceChangeListener mPreferencesChangeListener =
68        new OnSharedPreferenceChangeListener() {
69        public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
70            if (MessagingPreferenceActivity.AUTO_RETRIEVAL.equals(key)
71                    || MessagingPreferenceActivity.RETRIEVAL_DURING_ROAMING.equals(key)) {
72                if (LOCAL_LOGV) {
73                    Log.v(TAG, "Preferences updated.");
74                }
75
76                synchronized (sInstance) {
77                    mAutoDownload = getAutoDownloadState(prefs);
78                    if (LOCAL_LOGV) {
79                        Log.v(TAG, "mAutoDownload ------> " + mAutoDownload);
80                    }
81                }
82            }
83        }
84    };
85
86    private final BroadcastReceiver mRoamingStateListener =
87        new BroadcastReceiver() {
88        @Override
89        public void onReceive(Context context, Intent intent) {
90            if (TelephonyIntents.ACTION_SERVICE_STATE_CHANGED.equals(intent.getAction())) {
91                if (LOCAL_LOGV) {
92                    Log.v(TAG, "Service state changed: " + intent.getExtras());
93                }
94
95                ServiceState state = ServiceState.newFromBundle(intent.getExtras());
96                boolean isRoaming = state.getRoaming();
97                if (LOCAL_LOGV) {
98                    Log.v(TAG, "roaming ------> " + isRoaming);
99                }
100                synchronized (sInstance) {
101                    mAutoDownload = getAutoDownloadState(mPreferences, isRoaming);
102                    if (LOCAL_LOGV) {
103                        Log.v(TAG, "mAutoDownload ------> " + mAutoDownload);
104                    }
105                }
106            }
107        }
108    };
109
110    private static DownloadManager sInstance;
111
112    private DownloadManager(Context context) {
113        mContext = context;
114        mHandler = new Handler();
115        mPreferences = PreferenceManager.getDefaultSharedPreferences(context);
116        mPreferences.registerOnSharedPreferenceChangeListener(mPreferencesChangeListener);
117
118        context.registerReceiver(
119                mRoamingStateListener,
120                new IntentFilter(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED));
121
122        mAutoDownload = getAutoDownloadState(mPreferences);
123        if (LOCAL_LOGV) {
124            Log.v(TAG, "mAutoDownload ------> " + mAutoDownload);
125        }
126    }
127
128    public boolean isAuto() {
129        return mAutoDownload;
130    }
131
132    public static void init(Context context) {
133        if (LOCAL_LOGV) {
134            Log.v(TAG, "DownloadManager.init()");
135        }
136
137        if (sInstance != null) {
138            Log.w(TAG, "Already initialized.");
139        }
140        sInstance = new DownloadManager(context);
141    }
142
143    public static DownloadManager getInstance() {
144        if (sInstance == null) {
145            throw new IllegalStateException("Uninitialized.");
146        }
147        return sInstance;
148    }
149
150    static boolean getAutoDownloadState(SharedPreferences prefs) {
151        return getAutoDownloadState(prefs, isRoaming());
152    }
153
154    static boolean getAutoDownloadState(SharedPreferences prefs, boolean roaming) {
155        boolean autoDownload = prefs.getBoolean(
156                MessagingPreferenceActivity.AUTO_RETRIEVAL, true);
157
158        if (LOCAL_LOGV) {
159            Log.v(TAG, "auto download without roaming -> " + autoDownload);
160        }
161
162        if (autoDownload) {
163            boolean alwaysAuto = prefs.getBoolean(
164                    MessagingPreferenceActivity.RETRIEVAL_DURING_ROAMING, false);
165
166            if (LOCAL_LOGV) {
167                Log.v(TAG, "auto download during roaming -> " + alwaysAuto);
168            }
169
170            if (!roaming || alwaysAuto) {
171                return true;
172            }
173        }
174        return false;
175    }
176
177    static boolean isRoaming() {
178        // TODO: fix and put in Telephony layer
179        String roaming = SystemProperties.get(
180                TelephonyProperties.PROPERTY_OPERATOR_ISROAMING, null);
181        if (LOCAL_LOGV) {
182            Log.v(TAG, "roaming ------> " + roaming);
183        }
184        return "true".equals(roaming);
185    }
186
187    public void markState(final Uri uri, int state) {
188        // Notify user if the message has expired.
189        try {
190            NotificationInd nInd = (NotificationInd) PduPersister.getPduPersister(mContext)
191                    .load(uri);
192            if ((nInd.getExpiry() < System.currentTimeMillis()/1000L)
193                && (state == STATE_DOWNLOADING)) {
194                mHandler.post(new Runnable() {
195                    public void run() {
196                        Toast.makeText(mContext, R.string.service_message_not_found,
197                                Toast.LENGTH_LONG).show();
198                    }
199                });
200                SqliteWrapper.delete(mContext, mContext.getContentResolver(), uri, null, null);
201                return;
202            }
203        } catch(MmsException e) {
204            Log.e(TAG, e.getMessage(), e);
205            return;
206        }
207
208        // Notify user if downloading permanently failed.
209        if (state == STATE_PERMANENT_FAILURE) {
210            mHandler.post(new Runnable() {
211                public void run() {
212                    try {
213                        Toast.makeText(mContext, getMessage(uri),
214                                Toast.LENGTH_LONG).show();
215                    } catch (MmsException e) {
216                        Log.e(TAG, e.getMessage(), e);
217                    }
218                }
219            });
220        } else if (!mAutoDownload) {
221            state |= DEFERRED_MASK;
222        }
223
224        // Use the STATUS field to store the state of downloading process
225        // because it's useless for M-Notification.ind.
226        ContentValues values = new ContentValues(1);
227        values.put(Mms.STATUS, state);
228        SqliteWrapper.update(mContext, mContext.getContentResolver(),
229                    uri, values, null, null);
230    }
231
232    public void showErrorCodeToast(int errorStr) {
233        final int errStr = errorStr;
234        mHandler.post(new Runnable() {
235            public void run() {
236                try {
237                    Toast.makeText(mContext, errStr, Toast.LENGTH_LONG).show();
238                } catch (Exception e) {
239                    Log.e(TAG,"Caught an exception in showErrorCodeToast");
240                }
241            }
242        });
243    }
244
245    private String getMessage(Uri uri) throws MmsException {
246        NotificationInd ind = (NotificationInd) PduPersister
247                .getPduPersister(mContext).load(uri);
248
249        EncodedStringValue v = ind.getSubject();
250        String subject = (v != null) ? v.getString()
251                : mContext.getString(R.string.no_subject);
252
253        v = ind.getFrom();
254        String from = (v != null)
255                ? Contact.get(v.getString(), false).getName()
256                : mContext.getString(R.string.unknown_sender);
257
258        return mContext.getString(R.string.dl_failure_notification, subject, from);
259    }
260
261    public int getState(Uri uri) {
262        Cursor cursor = SqliteWrapper.query(mContext, mContext.getContentResolver(),
263                            uri, new String[] {Mms.STATUS}, null, null, null);
264
265        if (cursor != null) {
266            try {
267                if (cursor.moveToFirst()) {
268                    return cursor.getInt(0) & ~DEFERRED_MASK;
269                }
270            } finally {
271                cursor.close();
272            }
273        }
274        return STATE_UNSTARTED;
275    }
276}
277