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