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