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 org.w3c.dom.events.Event; 21 import org.w3c.dom.smil.ElementTime; 22 23 import android.content.ContentResolver; 24 import android.content.Context; 25 import android.database.Cursor; 26 import android.database.sqlite.SqliteWrapper; 27 import android.net.Uri; 28 import android.provider.MediaStore.Images; 29 import android.text.TextUtils; 30 import android.util.Log; 31 import android.webkit.MimeTypeMap; 32 33 import com.android.mms.ContentRestrictionException; 34 import com.android.mms.LogTag; 35 import com.android.mms.MmsApp; 36 import com.android.mms.dom.events.EventImpl; 37 import com.android.mms.dom.smil.SmilMediaElementImpl; 38 import com.android.mms.util.ItemLoadedCallback; 39 import com.android.mms.util.ItemLoadedFuture; 40 import com.android.mms.util.ThumbnailManager; 41 import android.provider.Telephony.Mms.Part; 42 import com.google.android.mms.ContentType; 43 import com.google.android.mms.MmsException; 44 45 public class VideoModel extends RegionMediaModel { 46 private static final String TAG = MediaModel.TAG; 47 private static final boolean DEBUG = true; 48 private static final boolean LOCAL_LOGV = false; 49 private ItemLoadedFuture mItemLoadedFuture; 50 51 public VideoModel(Context context, Uri uri, RegionModel region) 52 throws MmsException { 53 this(context, null, null, uri, region); 54 initModelFromUri(uri); 55 checkContentRestriction(); 56 } 57 58 public VideoModel(Context context, String contentType, String src, 59 Uri uri, RegionModel region) throws MmsException { 60 super(context, SmilHelper.ELEMENT_TAG_VIDEO, contentType, src, uri, region); 61 } 62 63 private void initModelFromUri(Uri uri) throws MmsException { 64 String scheme = uri.getScheme(); 65 if (scheme.equals("content")) { 66 initFromContentUri(uri); 67 } else if (uri.getScheme().equals("file")) { 68 initFromFile(uri); 69 } 70 initMediaDuration(); 71 } 72 73 private void initFromFile(Uri uri) { 74 String path = uri.getPath(); 75 mSrc = path.substring(path.lastIndexOf('/') + 1); 76 MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); 77 String extension = MimeTypeMap.getFileExtensionFromUrl(mSrc); 78 if (TextUtils.isEmpty(extension)) { 79 // getMimeTypeFromExtension() doesn't handle spaces in filenames nor can it handle 80 // urlEncoded strings. Let's try one last time at finding the extension. 81 int dotPos = mSrc.lastIndexOf('.'); 82 if (0 <= dotPos) { 83 extension = mSrc.substring(dotPos + 1); 84 } 85 } 86 mContentType = mimeTypeMap.getMimeTypeFromExtension(extension); 87 // It's ok if mContentType is null. Eventually we'll show a toast telling the 88 // user the video couldn't be attached. 89 90 if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) { 91 Log.v(TAG, "New VideoModel initFromFile created:" 92 + " mSrc=" + mSrc 93 + " mContentType=" + mContentType 94 + " mUri=" + uri); 95 } 96 } 97 98 private void initFromContentUri(Uri uri) throws MmsException { 99 ContentResolver cr = mContext.getContentResolver(); 100 Cursor c = SqliteWrapper.query(mContext, cr, uri, null, null, null, null); 101 102 if (c != null) { 103 try { 104 if (c.moveToFirst()) { 105 String path; 106 try { 107 // Local videos will have a data column 108 path = c.getString(c.getColumnIndexOrThrow(Images.Media.DATA)); 109 } catch (IllegalArgumentException e) { 110 // For non-local videos, the path is the uri 111 path = uri.toString(); 112 } 113 mSrc = path.substring(path.lastIndexOf('/') + 1); 114 if (VideoModel.isMmsUri(uri)) { 115 mContentType = c.getString(c.getColumnIndexOrThrow( 116 Part.CONTENT_TYPE)); 117 } else { 118 mContentType = c.getString(c.getColumnIndexOrThrow( 119 Images.Media.MIME_TYPE)); 120 } 121 if (TextUtils.isEmpty(mContentType)) { 122 throw new MmsException("Type of media is unknown."); 123 } 124 125 if (mContentType.equals(ContentType.VIDEO_MP4) && !(TextUtils.isEmpty(mSrc))) { 126 int index = mSrc.lastIndexOf("."); 127 if (index != -1) { 128 try { 129 String extension = mSrc.substring(index + 1); 130 if (!(TextUtils.isEmpty(extension)) && 131 (extension.equalsIgnoreCase("3gp") || 132 extension.equalsIgnoreCase("3gpp") || 133 extension.equalsIgnoreCase("3g2"))) { 134 mContentType = ContentType.VIDEO_3GPP; 135 } 136 } catch(IndexOutOfBoundsException ex) { 137 if (LOCAL_LOGV) { 138 Log.v(TAG, "Media extension is unknown."); 139 } 140 } 141 } 142 } 143 144 if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) { 145 Log.v(TAG, "New VideoModel initFromContentUri created:" 146 + " mSrc=" + mSrc 147 + " mContentType=" + mContentType 148 + " mUri=" + uri); 149 } 150 } else { 151 throw new MmsException("Nothing found: " + uri); 152 } 153 } finally { 154 c.close(); 155 } 156 } else { 157 throw new MmsException("Bad URI: " + uri); 158 } 159 } 160 161 // EventListener Interface 162 public void handleEvent(Event evt) { 163 String evtType = evt.getType(); 164 if (LOCAL_LOGV || Log.isLoggable(LogTag.APP, Log.VERBOSE)) { 165 Log.v(TAG, "[VideoModel] handleEvent " + evt.getType() + " on " + this); 166 } 167 168 MediaAction action = MediaAction.NO_ACTIVE_ACTION; 169 if (evtType.equals(SmilMediaElementImpl.SMIL_MEDIA_START_EVENT)) { 170 action = MediaAction.START; 171 172 // if the Music player app is playing audio, we should pause that so it won't 173 // interfere with us playing video here. 174 pauseMusicPlayer(); 175 176 mVisible = true; 177 } else if (evtType.equals(SmilMediaElementImpl.SMIL_MEDIA_END_EVENT)) { 178 action = MediaAction.STOP; 179 if (mFill != ElementTime.FILL_FREEZE) { 180 mVisible = false; 181 } 182 } else if (evtType.equals(SmilMediaElementImpl.SMIL_MEDIA_PAUSE_EVENT)) { 183 action = MediaAction.PAUSE; 184 mVisible = true; 185 } else if (evtType.equals(SmilMediaElementImpl.SMIL_MEDIA_SEEK_EVENT)) { 186 action = MediaAction.SEEK; 187 mSeekTo = ((EventImpl) evt).getSeekTo(); 188 mVisible = true; 189 } 190 191 appendAction(action); 192 notifyModelChanged(false); 193 } 194 195 protected void checkContentRestriction() throws ContentRestrictionException { 196 ContentRestriction cr = ContentRestrictionFactory.getContentRestriction(); 197 cr.checkVideoContentType(mContentType); 198 } 199 200 @Override 201 protected boolean isPlayable() { 202 return true; 203 } 204 205 public ItemLoadedFuture loadThumbnailBitmap(ItemLoadedCallback callback) { 206 ThumbnailManager thumbnailManager = MmsApp.getApplication().getThumbnailManager(); 207 mItemLoadedFuture = thumbnailManager.getVideoThumbnail(getUri(), callback); 208 return mItemLoadedFuture; 209 } 210 211 public void cancelThumbnailLoading() { 212 if (mItemLoadedFuture != null && !mItemLoadedFuture.isDone()) { 213 if (Log.isLoggable(LogTag.APP, Log.DEBUG)) { 214 Log.v(TAG, "cancelThumbnailLoading for: " + this); 215 } 216 mItemLoadedFuture.cancel(getUri()); 217 mItemLoadedFuture = null; 218 } 219 } 220 } 221