ProjectPickerAdapter.java revision 7347ed1e162009eeeb3113eafaebbc39a9f67fda
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 LruCache<String, Bitmap> mPreviewBitmapCache;
55
56    public ProjectPickerAdapter(Context context, LayoutInflater inflater,
57            List<VideoEditorProject> projects) {
58        mContext = context;
59        mResources = context.getResources();
60        mInflater = inflater;
61        mProjects = projects;
62        mItemWidth = (int) mResources.getDimension(R.dimen.project_picker_item_width);
63        mItemHeight = (int) mResources.getDimension(R.dimen.project_picker_item_height);
64
65        // Limit the cache size to 15 thumbnails.
66        mPreviewBitmapCache = new LruCache<String, Bitmap>(15);
67    }
68
69    /**
70     * Clears project list and update display.
71     */
72    public void clear() {
73        mProjects.clear();
74        notifyDataSetChanged();
75    }
76
77    /**
78     * Removes the project with specified {@code projectPath} from the project list and updates the
79     * display.
80     *
81     * @param projectPath The project path of the to-be-removed project
82     * @return {code true} if the project is successfully removed,
83     *      {code false} if no removal happened
84     */
85    public boolean remove(String projectPath) {
86        for (VideoEditorProject project : mProjects) {
87            if (project.getPath().equals(projectPath)) {
88                if (mProjects.remove(project)) {
89                    notifyDataSetChanged();
90                    return true;
91                } else {
92                    return false;
93                }
94            }
95        }
96        return false;
97    }
98
99    @Override
100    public int getCount() {
101        // Add one to represent an additional dummy project for "create new project" option.
102        return mProjects.size() + 1;
103    }
104
105    @Override
106    public Object getItem(int position) {
107        if (position == mProjects.size()) {
108            return null;
109        }
110        return mProjects.get(position);
111    }
112
113    @Override
114    public long getItemId(int position) {
115        return position;
116    }
117
118    @Override
119    public View getView(int position, View convertView, ViewGroup parent) {
120        // Inflate a new view with project thumbnail and information.
121        // We never reuse convertView because we load thumbnails asynchronously
122        // and hook an async task with the new view. If the new view is reused
123        // as a convertView, the async task might put a wrong thumbnail on it.
124        View v = mInflater.inflate(R.layout.project_picker_item, null);
125        ImageView iv = (ImageView) v.findViewById(R.id.thumbnail);
126        Bitmap thumbnail;
127        TextView titleView = (TextView) v.findViewById(R.id.title);
128        TextView durationView = (TextView) v.findViewById(R.id.duration);
129        if (position == mProjects.size()) {
130            thumbnail = renderNewProjectThumbnail();
131            titleView.setTypeface(Typeface.DEFAULT, Typeface.ITALIC);
132            titleView.setText(mContext.getString(R.string.projects_new_project));
133            durationView.setText("");
134        } else {
135            VideoEditorProject project = mProjects.get(position);
136            thumbnail = getThumbnail(project.getPath(), iv);
137            titleView.setText(project.getName());
138            durationView.setText(millisecondsToTimeString(project.getProjectDuration()));
139        }
140
141        if (thumbnail != null) {
142            iv.setImageBitmap(thumbnail);
143        }
144
145        return v;
146    }
147
148    private Bitmap getThumbnail(String projectPath, ImageView imageView) {
149        Bitmap previewBitmap = mPreviewBitmapCache.get(projectPath);
150        if (previewBitmap == null) {
151            // Cache miss: asynchronously load bitmap to avoid scroll stuttering
152            // in the project picker.
153            new LoadPreviewBitmapTask(projectPath, imageView, mItemWidth, mItemHeight,
154                    mPreviewBitmapCache).execute();
155        } else {
156            return previewBitmap;
157        }
158
159        return null;
160    }
161
162    private Bitmap renderNewProjectThumbnail() {
163        final Bitmap bitmap = Bitmap.createBitmap(mItemWidth, mItemHeight,
164                Bitmap.Config.ARGB_8888);
165        final Canvas canvas = new Canvas(bitmap);
166        final Paint paint = new Paint();
167
168        paint.setColor(Color.WHITE);
169        paint.setStyle(Style.STROKE);
170        // Hairline mode, 1 pixel wide.
171        paint.setStrokeWidth(0);
172
173        canvas.drawRect(0, 0, mItemWidth-1, mItemHeight-1, paint);
174
175        paint.setTextSize(18.0f);
176        paint.setAlpha(255);
177        final Bitmap newProjectIcon = BitmapFactory.decodeResource(mResources,
178                R.drawable.ic_menu_add_video);
179        final int x = (mItemWidth - newProjectIcon.getWidth()) / 2;
180        final int y = (mItemHeight - newProjectIcon.getHeight()) / 2;
181        canvas.drawBitmap(newProjectIcon, x, y, paint);
182        newProjectIcon.recycle();
183
184        return bitmap;
185    }
186
187    /**
188     * Converts milliseconds into the string time format HH:mm:ss.
189     */
190    private String millisecondsToTimeString(long milliseconds) {
191        return DateUtils.formatElapsedTime(milliseconds / 1000);
192    }
193}
194
195/**
196 * Worker that loads preview bitmap for a project,
197 */
198class LoadPreviewBitmapTask extends AsyncTask<Void, Void, Bitmap> {
199    private String mProjectPath;
200    // Handle to the image view we should update when the preview bitmap is loaded.
201    private ImageView mImageView;
202    private int mWidth;
203    private int mHeight;
204    private LruCache<String, Bitmap> mPreviewBitmapCache;
205
206    public LoadPreviewBitmapTask(String projectPath, ImageView imageView,
207            int width, int height, LruCache<String, Bitmap> previewBitmapCache) {
208        mProjectPath = projectPath;
209        mImageView = imageView;
210        mWidth = width;
211        mHeight = height;
212        mPreviewBitmapCache = previewBitmapCache;
213    }
214
215    @Override
216    protected Bitmap doInBackground(Void... param) {
217        final File thumbnail = new File(mProjectPath, VideoEditor.THUMBNAIL_FILENAME);
218        // Return early if thumbnail does not exist.
219        if (!thumbnail.exists()) {
220            return null;
221        }
222
223        try {
224            final Bitmap previewBitmap = ImageUtils.scaleImage(
225                    thumbnail.getAbsolutePath(),
226                    mWidth - 10,
227                    mHeight - 10,
228                    ImageUtils.MATCH_SMALLER_DIMENSION);
229            if (previewBitmap != null) {
230                final Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight,
231                        Bitmap.Config.ARGB_8888);
232                final Canvas canvas = new Canvas(bitmap);
233                final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
234
235                // Draw bitmap at the center of the canvas.
236                canvas.drawBitmap(previewBitmap,
237                        (mWidth - previewBitmap.getWidth()) / 2,
238                        (mHeight - previewBitmap.getHeight()) / 2,
239                        paint);
240                return bitmap;
241            }
242        } catch (IOException e) {
243            e.printStackTrace();
244        }
245        return null;
246    }
247
248    @Override
249    protected void onPostExecute(Bitmap result) {
250        // If we successfully load the preview bitmap, update the image view.
251        if (result != null) {
252            mPreviewBitmapCache.put(mProjectPath, result);
253            mImageView.setImageBitmap(result);
254        }
255    }
256}
257