• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.videoeditor;
18 
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.graphics.Bitmap;
22 import android.graphics.BitmapFactory;
23 import android.graphics.Canvas;
24 import android.graphics.Color;
25 import android.graphics.Paint;
26 import android.graphics.Paint.Style;
27 import android.graphics.Typeface;
28 import android.media.videoeditor.VideoEditor;
29 import android.os.AsyncTask;
30 import android.text.format.DateUtils;
31 import android.text.TextPaint;
32 import android.text.TextUtils;
33 import android.util.Log;
34 import android.util.LruCache;
35 import android.view.LayoutInflater;
36 import android.view.View;
37 import android.view.ViewGroup;
38 import android.widget.BaseAdapter;
39 import android.widget.ImageView;
40 import android.widget.TextView;
41 
42 import com.android.videoeditor.service.VideoEditorProject;
43 import com.android.videoeditor.util.ImageUtils;
44 
45 import java.io.File;
46 import java.io.IOException;
47 import java.util.List;
48 
49 
50 public class ProjectPickerAdapter extends BaseAdapter {
51     private Context mContext;
52     private Resources mResources;
53     private LayoutInflater mInflater;
54     private List<VideoEditorProject> mProjects;
55     private int mItemWidth;
56     private int mItemHeight;
57     private int mOverlayHeight;
58     private int mOverlayVerticalInset;
59     private int mOverlayHorizontalInset;
60     private LruCache<String, Bitmap> mPreviewBitmapCache;
61 
ProjectPickerAdapter(Context context, LayoutInflater inflater, List<VideoEditorProject> projects)62     public ProjectPickerAdapter(Context context, LayoutInflater inflater,
63             List<VideoEditorProject> projects) {
64         mContext = context;
65         mResources = context.getResources();
66         mInflater = inflater;
67         mProjects = projects;
68         mItemWidth = (int) mResources.getDimension(R.dimen.project_picker_item_width);
69         mItemHeight = (int) mResources.getDimension(R.dimen.project_picker_item_height);
70         mOverlayHeight = (int) mResources.getDimension(
71                 R.dimen.project_picker_item_overlay_height);
72         mOverlayVerticalInset = (int) mResources.getDimension(
73                 R.dimen.project_picker_item_overlay_vertical_inset);
74         mOverlayHorizontalInset = (int) mResources.getDimension(
75                 R.dimen.project_picker_item_overlay_horizontal_inset);
76         // Limit the cache size to 15 thumbnails.
77         mPreviewBitmapCache = new LruCache<String, Bitmap>(15);
78     }
79 
80     /**
81      * Clears project list and update display.
82      */
clear()83     public void clear() {
84         mPreviewBitmapCache.evictAll();
85         mProjects.clear();
86         notifyDataSetChanged();
87     }
88 
89     /**
90      * Removes the project with specified {@code projectPath} from the project list and updates the
91      * display.
92      *
93      * @param projectPath The project path of the to-be-removed project
94      * @return {@code true} if the project is successfully removed,
95      *      {@code false} if no removal happened
96      */
remove(String projectPath)97     public boolean remove(String projectPath) {
98         for (VideoEditorProject project : mProjects) {
99             if (project.getPath().equals(projectPath)) {
100                 if (mProjects.remove(project)) {
101                     notifyDataSetChanged();
102                     return true;
103                 } else {
104                     return false;
105                 }
106             }
107         }
108         return false;
109     }
110 
111     @Override
getCount()112     public int getCount() {
113         // Add one to represent an additional dummy project for "create new project" option.
114         return mProjects.size() + 1;
115     }
116 
117     @Override
getItem(int position)118     public Object getItem(int position) {
119         if (position == mProjects.size()) {
120             return null;
121         }
122         return mProjects.get(position);
123     }
124 
125     @Override
getItemId(int position)126     public long getItemId(int position) {
127         return position;
128     }
129 
130     @Override
getView(int position, View convertView, ViewGroup parent)131     public View getView(int position, View convertView, ViewGroup parent) {
132         // Inflate a new view with project thumbnail and information.
133         // We never reuse convertView because we load thumbnails asynchronously
134         // and hook an async task with the new view. If the new view is reused
135         // as a convertView, the async task might put a wrong thumbnail on it.
136         View v = mInflater.inflate(R.layout.project_picker_item, null);
137         ImageView iv = (ImageView) v.findViewById(R.id.thumbnail);
138         Bitmap thumbnail;
139         String title;
140         String duration;
141         if (position == mProjects.size()) {
142             title = mContext.getString(R.string.projects_new_project);
143             duration = "";
144             thumbnail = renderNewProjectThumbnail();
145         } else {
146             VideoEditorProject project = mProjects.get(position);
147             title = project.getName();
148             if (title == null) {
149                 title = "";
150             }
151             duration = millisecondsToTimeString(project.getProjectDuration());
152             thumbnail = getThumbnail(project.getPath(), iv, title, duration);
153         }
154 
155         if (thumbnail != null) {
156             drawBottomOverlay(thumbnail, title, duration);
157             iv.setImageBitmap(thumbnail);
158         }
159 
160         return v;
161     }
162 
163     /**
164      * Draws transparent black bottom overlay with movie title and duration on the bitmap.
165      */
drawBottomOverlay(Bitmap bitmap, String title, String duration)166     public void drawBottomOverlay(Bitmap bitmap, String title, String duration) {
167         // Draw overlay at the bottom of the canvas.
168         final Canvas canvas = new Canvas(bitmap);
169         final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
170         paint.setColor(Color.BLACK);
171         paint.setAlpha(128);
172         final int left = 0, top = bitmap.getHeight() - mOverlayHeight,
173                 right = bitmap.getWidth(), bottom = bitmap.getHeight();
174         canvas.drawRect(left, top, right, bottom, paint);
175 
176         paint.setColor(Color.WHITE);
177         paint.setTextSize((int) mResources.getDimension(R.dimen.project_picker_item_font_size));
178 
179         // Draw movie title at the left of the overlay. Trim title if it is going to overlap with
180         // duration text.
181         final int availableTitleWidth = bitmap.getWidth() - (int) paint.measureText(duration);
182         title = TextUtils.ellipsize(title, new TextPaint(paint), availableTitleWidth,
183                 TextUtils.TruncateAt.END).toString();
184         canvas.drawText(title, mOverlayHorizontalInset,
185                 bitmap.getHeight() - mOverlayHeight + mOverlayVerticalInset,
186                 paint);
187 
188         // Draw movie duration at the right of the overlay.
189         canvas.drawText(duration,
190                 bitmap.getWidth() - paint.measureText(duration) - mOverlayHorizontalInset,
191                 bitmap.getHeight() - mOverlayHeight + mOverlayVerticalInset,
192                 paint);
193     }
194 
getThumbnail(String projectPath, ImageView imageView, String title, String duration)195     private Bitmap getThumbnail(String projectPath, ImageView imageView, String title,
196             String duration) {
197         Bitmap previewBitmap = mPreviewBitmapCache.get(projectPath);
198         if (previewBitmap == null) {
199             // Cache miss: asynchronously load bitmap to avoid scroll stuttering
200             // in the project picker.
201             new LoadPreviewBitmapTask(this, projectPath, imageView, mItemWidth, mItemHeight,
202                     title, duration, mPreviewBitmapCache).execute();
203         } else {
204             return previewBitmap;
205         }
206 
207         return null;
208     }
209 
renderNewProjectThumbnail()210     private Bitmap renderNewProjectThumbnail() {
211         final Bitmap bitmap = Bitmap.createBitmap(mItemWidth, mItemHeight,
212                 Bitmap.Config.ARGB_8888);
213         final Canvas canvas = new Canvas(bitmap);
214         final Paint paint = new Paint();
215         canvas.drawRect(0, 0, mItemWidth, mItemHeight, paint);
216 
217         paint.setTextSize(18.0f);
218         paint.setAlpha(255);
219         final Bitmap newProjectIcon = BitmapFactory.decodeResource(mResources,
220                 R.drawable.add_video_project_big);
221         final int x = (mItemWidth - newProjectIcon.getWidth()) / 2;
222         final int y = (mItemHeight - newProjectIcon.getHeight()) / 2;
223         canvas.drawBitmap(newProjectIcon, x, y, paint);
224         newProjectIcon.recycle();
225 
226         return bitmap;
227     }
228 
229     /**
230      * Converts milliseconds into the string time format HH:mm:ss.
231      */
millisecondsToTimeString(long milliseconds)232     private String millisecondsToTimeString(long milliseconds) {
233         return DateUtils.formatElapsedTime(milliseconds / 1000);
234     }
235 }
236 
237 /**
238  * Worker that loads preview bitmap for a project,
239  */
240 class LoadPreviewBitmapTask extends AsyncTask<Void, Void, Bitmap> {
241     // Handle to the adapter that initiates this async task.
242     private ProjectPickerAdapter mContextAdapter;
243     private String mProjectPath;
244     // Handle to the image view we should update when the preview bitmap is loaded.
245     private ImageView mImageView;
246     private int mWidth;
247     private int mHeight;
248     private String mTitle;
249     private String mDuration;
250     private LruCache<String, Bitmap> mPreviewBitmapCache;
251 
LoadPreviewBitmapTask(ProjectPickerAdapter contextAdapter, String projectPath, ImageView imageView, int width, int height, String title, String duration, LruCache<String, Bitmap> previewBitmapCache)252     public LoadPreviewBitmapTask(ProjectPickerAdapter contextAdapter, String projectPath,
253             ImageView imageView, int width, int height, String title, String duration,
254             LruCache<String, Bitmap> previewBitmapCache) {
255         mContextAdapter = contextAdapter;
256         mProjectPath = projectPath;
257         mImageView = imageView;
258         mWidth = width;
259         mHeight = height;
260         mTitle = title;
261         mDuration = duration;
262         mPreviewBitmapCache = previewBitmapCache;
263     }
264 
265     @Override
doInBackground(Void... param)266     protected Bitmap doInBackground(Void... param) {
267         final File thumbnail = new File(mProjectPath, VideoEditor.THUMBNAIL_FILENAME);
268         // Return early if thumbnail does not exist.
269         if (!thumbnail.exists()) {
270             return null;
271         }
272 
273         try {
274             final Bitmap previewBitmap = ImageUtils.scaleImage(
275                     thumbnail.getAbsolutePath(),
276                     mWidth,
277                     mHeight,
278                     ImageUtils.MATCH_LARGER_DIMENSION);
279             if (previewBitmap != null) {
280                 final Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight,
281                         Bitmap.Config.ARGB_8888);
282                 final Canvas canvas = new Canvas(bitmap);
283                 final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
284 
285                 // Draw bitmap at the center of the canvas.
286                 canvas.drawBitmap(previewBitmap,
287                         (mWidth - previewBitmap.getWidth()) / 2,
288                         (mHeight - previewBitmap.getHeight()) / 2,
289                         paint);
290                 return bitmap;
291             }
292         } catch (IOException e) {
293             e.printStackTrace();
294         }
295 
296         return null;
297     }
298 
299     @Override
onPostExecute(Bitmap result)300     protected void onPostExecute(Bitmap result) {
301         if (result == null) {
302             // If we don't have thumbnail, default to a black canvas.
303             result = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
304             result.eraseColor(Color.BLACK);
305         } else {
306             mPreviewBitmapCache.put(mProjectPath, result);
307         }
308 
309         // Update the image view.
310         mContextAdapter.drawBottomOverlay(result, mTitle, mDuration);
311         mImageView.setImageBitmap(result);
312     }
313 }
314