Home | History | Annotate | Download | only in videoeditor
      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 
     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      */
     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      */
     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
    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
    118     public Object getItem(int position) {
    119         if (position == mProjects.size()) {
    120             return null;
    121         }
    122         return mProjects.get(position);
    123     }
    124 
    125     @Override
    126     public long getItemId(int position) {
    127         return position;
    128     }
    129 
    130     @Override
    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      */
    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 
    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 
    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      */
    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 
    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
    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
    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