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