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.model;
19
20import java.io.FileInputStream;
21import java.io.FileNotFoundException;
22import java.io.IOException;
23import java.io.InputStream;
24import java.util.ArrayList;
25
26import org.w3c.dom.events.EventListener;
27
28import android.content.ContentResolver;
29import android.content.Context;
30import android.content.Intent;
31import android.media.MediaMetadataRetriever;
32import android.net.Uri;
33import android.util.Log;
34
35import com.android.mms.LogTag;
36import com.android.mms.MmsConfig;
37import com.google.android.mms.MmsException;
38// TODO: remove dependency for SDK build
39
40public abstract class MediaModel extends Model implements EventListener {
41    protected static final String TAG = "Mms/media";
42
43    private final static String MUSIC_SERVICE_ACTION = "com.android.music.musicservicecommand";
44
45    protected Context mContext;
46    protected int mBegin;
47    protected int mDuration;
48    protected String mTag;
49    protected String mSrc;
50    protected String mContentType;
51    private Uri mUri;
52    private byte[] mData;
53    protected short mFill;
54    protected int mSize;
55    protected int mSeekTo;
56    protected boolean mMediaResizeable;
57
58    private final ArrayList<MediaAction> mMediaActions;
59    public static enum MediaAction {
60        NO_ACTIVE_ACTION,
61        START,
62        STOP,
63        PAUSE,
64        SEEK,
65    }
66
67    public MediaModel(Context context, String tag, String contentType,
68            String src, Uri uri) throws MmsException {
69        mContext = context;
70        mTag = tag;
71        mContentType = contentType;
72        mSrc = src;
73        mUri = uri;
74        initMediaSize();
75        mMediaActions = new ArrayList<MediaAction>();
76    }
77
78    public MediaModel(Context context, String tag, String contentType,
79            String src, byte[] data) {
80        if (data == null) {
81            throw new IllegalArgumentException("data may not be null.");
82        }
83
84        mContext = context;
85        mTag = tag;
86        mContentType = contentType;
87        mSrc = src;
88        mData = data;
89        mSize = data.length;
90        mMediaActions = new ArrayList<MediaAction>();
91    }
92
93    public int getBegin() {
94        return mBegin;
95    }
96
97    public void setBegin(int begin) {
98        mBegin = begin;
99        notifyModelChanged(true);
100    }
101
102    public int getDuration() {
103        return mDuration;
104    }
105
106    public void setDuration(int duration) {
107        if (isPlayable() && (duration < 0)) {
108            // 'indefinite' duration, we should try to find its exact value;
109            try {
110                initMediaDuration();
111            } catch (MmsException e) {
112                // On error, keep default duration.
113                Log.e(TAG, e.getMessage(), e);
114                return;
115            }
116        } else {
117            mDuration = duration;
118        }
119        notifyModelChanged(true);
120    }
121
122    public String getTag() {
123        return mTag;
124    }
125
126    public String getContentType() {
127        return mContentType;
128    }
129
130    /**
131     * Get the URI of the media.
132     *
133     * @return The URI of the media.
134     */
135    public Uri getUri() {
136        return mUri;
137    }
138
139    public byte[] getData() {
140        if (mData != null) {
141            byte[] data = new byte[mData.length];
142            System.arraycopy(mData, 0, data, 0, mData.length);
143            return data;
144        }
145        return null;
146    }
147
148    /**
149     * @param uri the mUri to set
150     */
151    void setUri(Uri uri) {
152        mUri = uri;
153    }
154
155    /**
156     * @return the mSrc
157     */
158    public String getSrc() {
159        return mSrc;
160    }
161
162    /**
163     * @return the mFill
164     */
165    public short getFill() {
166        return mFill;
167    }
168
169    /**
170     * @param fill the mFill to set
171     */
172    public void setFill(short fill) {
173        mFill = fill;
174        notifyModelChanged(true);
175    }
176
177    /**
178     * @return whether the media is resizable or not. For instance, a picture can be resized
179     * to smaller dimensions or lower resolution. Other media, such as video and sounds, aren't
180     * currently able to be resized.
181     */
182    public boolean getMediaResizable() {
183        return mMediaResizeable;
184    }
185
186    /**
187     * @return the size of the attached media
188     */
189    public int getMediaSize() {
190        return mSize;
191    }
192
193    public boolean isText() {
194        return mTag.equals(SmilHelper.ELEMENT_TAG_TEXT);
195    }
196
197    public boolean isImage() {
198        return mTag.equals(SmilHelper.ELEMENT_TAG_IMAGE);
199    }
200
201    public boolean isVideo() {
202        return mTag.equals(SmilHelper.ELEMENT_TAG_VIDEO);
203    }
204
205    public boolean isAudio() {
206        return mTag.equals(SmilHelper.ELEMENT_TAG_AUDIO);
207    }
208
209    protected void initMediaDuration() throws MmsException {
210        if (mUri == null) {
211            throw new IllegalArgumentException("Uri may not be null.");
212        }
213
214        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
215        int duration = 0;
216        try {
217            retriever.setDataSource(mContext, mUri);
218            String dur = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
219            if (dur != null) {
220                duration = Integer.parseInt(dur);
221            }
222            mDuration = duration;
223        } catch (Exception ex) {
224            Log.e(TAG, "MediaMetadataRetriever failed to get duration for " + mUri.getPath(), ex);
225            throw new MmsException(ex);
226        } finally {
227            retriever.release();
228        }
229    }
230
231    private void initMediaSize() throws MmsException {
232        ContentResolver cr = mContext.getContentResolver();
233        InputStream input = null;
234        try {
235            input = cr.openInputStream(mUri);
236            if (input instanceof FileInputStream) {
237                // avoid reading the whole stream to get its length
238                FileInputStream f = (FileInputStream) input;
239                mSize = (int) f.getChannel().size();
240                // sometimes mSize will be zero here. It's tempting to count the bytes as the code
241                // does below, but that turns out to be very slow. We'll deal with a zero size
242                // when we resize the media.
243
244                if (isVideo() && mSize > MmsConfig.getMaxMessageSize()) {
245                    Log.w(TAG, "initMediaSize: Video size: f.getChannel().size(): " + mSize +
246                            " larger than max message size: " + MmsConfig.getMaxMessageSize());
247                }
248            } else {
249                while (-1 != input.read()) {
250                    mSize++;
251                }
252            }
253
254        } catch (IOException e) {
255            // Ignore
256            Log.e(TAG, "IOException caught while opening or reading stream", e);
257            if (e instanceof FileNotFoundException) {
258                throw new MmsException(e.getMessage());
259            }
260        } finally {
261            if (null != input) {
262                try {
263                    input.close();
264                } catch (IOException e) {
265                    // Ignore
266                    Log.e(TAG, "IOException caught while closing stream", e);
267                }
268            }
269        }
270    }
271
272    public static boolean isMmsUri(Uri uri) {
273        return uri.getAuthority().startsWith("mms");
274    }
275
276    public int getSeekTo() {
277        return mSeekTo;
278    }
279
280    public void appendAction(MediaAction action) {
281        mMediaActions.add(action);
282    }
283
284    public MediaAction getCurrentAction() {
285        if (0 == mMediaActions.size()) {
286            return MediaAction.NO_ACTIVE_ACTION;
287        }
288        return mMediaActions.remove(0);
289    }
290
291    protected boolean isPlayable() {
292        return false;
293    }
294
295    protected void pauseMusicPlayer() {
296        if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
297            Log.d(TAG, "pauseMusicPlayer");
298        }
299
300        Intent i = new Intent(MUSIC_SERVICE_ACTION);
301        i.putExtra("command", "pause");
302        mContext.sendBroadcast(i);
303    }
304
305    /**
306     * If the attached media is resizeable, resize it to fit within the byteLimit. Save the
307     * new part in the pdu.
308     * @param byteLimit the max size of the media attachment
309     * @throws MmsException
310     */
311    protected void resizeMedia(int byteLimit, long messageId) throws MmsException {
312    }
313}
314