• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2 
3  * Copyright (C) 2011 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.videoeditor.widgets;
19 
20 import com.android.videoeditor.service.ApiService;
21 import com.android.videoeditor.service.MovieMediaItem;
22 import com.android.videoeditor.R;
23 
24 import android.content.Context;
25 import android.graphics.Bitmap;
26 import android.graphics.Canvas;
27 import android.graphics.Color;
28 import android.graphics.Paint;
29 import android.graphics.Point;
30 import android.graphics.Rect;
31 import android.graphics.drawable.Drawable;
32 import android.util.AttributeSet;
33 import android.util.DisplayMetrics;
34 import android.util.Log;
35 import android.util.LruCache;
36 import android.view.Display;
37 import android.view.GestureDetector;
38 import android.view.MotionEvent;
39 import android.view.View;
40 import android.view.WindowManager;
41 
42 import java.util.ArrayList;
43 import java.util.HashSet;
44 import java.util.Map;
45 
46 /**
47  * Media item preview view on the timeline. This class assumes the media item is always put on a
48  * MediaLinearLayout and is wrapped with a timeline scroll view.
49  */
50 public class MediaItemView extends View {
51     private static final String TAG = "MediaItemView";
52 
53     // Static variables
54     private static Drawable sAddTransitionDrawable;
55     private static Drawable sEmptyFrameDrawable;
56     private static ThumbnailCache sThumbnailCache;
57 
58     // Because MediaItemView may be recreated for the same MediaItem (it happens
59     // when the device orientation is changed), we use a globally unique
60     // generation counter to reject thumbnail results (passed to setBitmap())
61     // requested by a previous incarnation of MediaItemView.
62     private static int sGenerationCounter;
63 
64     // Instance variables
65     private final GestureDetector mGestureDetector;
66     private final ScrollViewListener mScrollListener;
67     private final Rect mGeneratingEffectProgressDestRect;
68 
69     private boolean mIsScrolling;
70     private boolean mIsPlaying;
71 
72     // Progress of generation of the effect applied on this media item view.
73     // -1 indicates the generation is not in progress. 0-100 indicates the
74     // generation is in progress. Currently only Ken Burns effect is used with
75     // the progress bar.
76     private int mGeneratingEffectProgress;
77 
78     // The scrolled left pixels of this view.
79     private int mScrollX;
80 
81     private String mProjectPath;
82     private MovieMediaItem mMediaItem;
83     // Convenient handle to the parent timeline scroll view.
84     private TimelineHorizontalScrollView mScrollView;
85     // Convenient handle to the parent timeline linear layout.
86     private MediaLinearLayout mTimeline;
87     private ItemSimpleGestureListener mGestureListener;
88     private int[] mLeftState, mRightState;
89 
90     private int mScreenWidth;
91     private int mThumbnailWidth, mThumbnailHeight;
92     private int mNumberOfThumbnails;
93     private long mBeginTimeMs, mEndTimeMs;
94 
95     private int mGeneration;
96     private HashSet<Integer> mPending;
97     private ArrayList<Integer> mWantThumbnails;
98 
MediaItemView(Context context, AttributeSet attrs)99     public MediaItemView(Context context, AttributeSet attrs) {
100         this(context, attrs, 0);
101     }
102 
MediaItemView(Context context)103     public MediaItemView(Context context) {
104         this(context, null, 0);
105     }
106 
MediaItemView(Context context, AttributeSet attrs, int defStyle)107     public MediaItemView(Context context, AttributeSet attrs, int defStyle) {
108         super(context, attrs, defStyle);
109 
110         // Initialize static data
111         if (sAddTransitionDrawable == null) {
112             sAddTransitionDrawable = getResources().getDrawable(
113                     R.drawable.add_transition_selector);
114             sEmptyFrameDrawable = getResources().getDrawable(
115                     R.drawable.timeline_loading);
116 
117             // Initialize the thumbnail cache, limit the memory usage to 3MB
118             sThumbnailCache = new ThumbnailCache(3*1024*1024);
119         }
120 
121         // Get the screen width
122         final Display display = ((WindowManager)context.getSystemService(
123                 Context.WINDOW_SERVICE)).getDefaultDisplay();
124         final DisplayMetrics metrics = new DisplayMetrics();
125         display.getMetrics(metrics);
126         mScreenWidth = metrics.widthPixels;
127 
128         // Setup our gesture detector and scroll listener
129         mGestureDetector = new GestureDetector(context, new MyGestureListener());
130         mScrollListener = new MyScrollViewListener();
131 
132         // Prepare the progress bar rectangles
133         final ProgressBar progressBar = ProgressBar.getProgressBar(context);
134         final int layoutHeight = (int)(
135                 getResources().getDimension(R.dimen.media_layout_height) -
136                 getResources().getDimension(R.dimen.media_layout_padding));
137         mGeneratingEffectProgressDestRect = new Rect(getPaddingLeft(),
138                 layoutHeight - progressBar.getHeight() - getPaddingBottom(), 0,
139                 layoutHeight - getPaddingBottom());
140 
141         // Initialize the progress value
142         mGeneratingEffectProgress = -1;
143 
144         // Initialize the "Add transition" indicators state
145         mLeftState = View.EMPTY_STATE_SET;
146         mRightState = View.EMPTY_STATE_SET;
147 
148         // Initialize the thumbnail indices we want to request
149         mWantThumbnails = new ArrayList<Integer>();
150 
151         // Initialize the set of indices we are waiting
152         mPending = new HashSet<Integer>();
153 
154         // Initialize the generation number
155         mGeneration = sGenerationCounter++;
156     }
157 
158     private class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
159         @Override
onSingleTapConfirmed(MotionEvent e)160         public boolean onSingleTapConfirmed(MotionEvent e) {
161             if (mGestureListener == null) {
162                 return false;
163             }
164 
165             int tappedArea = ItemSimpleGestureListener.CENTER_AREA;
166 
167             if (hasSpaceForAddTransitionIcons()) {
168                 if (mMediaItem.getBeginTransition() == null &&
169                         e.getX() < sAddTransitionDrawable.getIntrinsicWidth() +
170                         getPaddingLeft()) {
171                     tappedArea = ItemSimpleGestureListener.LEFT_AREA;
172                 } else if (mMediaItem.getEndTransition() == null &&
173                         e.getX() >= getWidth() - getPaddingRight() -
174                         sAddTransitionDrawable.getIntrinsicWidth()) {
175                     tappedArea = ItemSimpleGestureListener.RIGHT_AREA;
176                 }
177             }
178             return mGestureListener.onSingleTapConfirmed(
179                     MediaItemView.this, tappedArea, e);
180         }
181 
182         @Override
onLongPress(MotionEvent e)183         public void onLongPress(MotionEvent e) {
184             if (mGestureListener != null) {
185                 mGestureListener.onLongPress(MediaItemView.this, e);
186             }
187         }
188     }
189 
190     private class MyScrollViewListener implements ScrollViewListener {
191         @Override
onScrollBegin(View view, int scrollX, int scrollY, boolean appScroll)192         public void onScrollBegin(View view, int scrollX, int scrollY, boolean appScroll) {
193             mIsScrolling = true;
194         }
195 
196         @Override
onScrollProgress(View view, int scrollX, int scrollY, boolean appScroll)197         public void onScrollProgress(View view, int scrollX, int scrollY, boolean appScroll) {
198             mScrollX = scrollX;
199             invalidate();
200         }
201 
202         @Override
onScrollEnd(View view, int scrollX, int scrollY, boolean appScroll)203         public void onScrollEnd(View view, int scrollX, int scrollY, boolean appScroll) {
204             mIsScrolling = false;
205             mScrollX = scrollX;
206             invalidate();
207         }
208     }
209 
210     @Override
onAttachedToWindow()211     protected void onAttachedToWindow() {
212         mMediaItem = (MovieMediaItem) getTag();
213 
214         mScrollView = (TimelineHorizontalScrollView) getRootView().findViewById(
215                 R.id.timeline_scroller);
216         mScrollView.addScrollListener(mScrollListener);
217         // Add the horizontal scroll view listener
218         mScrollX = mScrollView.getScrollX();
219 
220         mTimeline = (MediaLinearLayout) getRootView().findViewById(R.id.timeline_media);
221     }
222 
223     @Override
onDetachedFromWindow()224     protected void onDetachedFromWindow() {
225         mScrollView.removeScrollListener(mScrollListener);
226         // Release the cached bitmaps
227         releaseBitmapsAndClear();
228     }
229 
230     /**
231      * @return The shadow builder
232      */
getShadowBuilder()233     public DragShadowBuilder getShadowBuilder() {
234         return new MediaItemShadowBuilder(this);
235     }
236 
237     /**
238      * Shadow builder for the media item
239      */
240     private class MediaItemShadowBuilder extends DragShadowBuilder {
241         private final Drawable mFrame;
242 
MediaItemShadowBuilder(View view)243         public MediaItemShadowBuilder(View view) {
244             super(view);
245             mFrame = view.getContext().getResources().getDrawable(
246                     R.drawable.timeline_item_pressed);
247         }
248 
249         @Override
onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint)250         public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
251             shadowSize.set(getShadowWidth(), getShadowHeight());
252             shadowTouchPoint.set(shadowSize.x / 2, shadowSize.y);
253         }
254 
255         @Override
onDrawShadow(Canvas canvas)256         public void onDrawShadow(Canvas canvas) {
257             mFrame.setBounds(0, 0, getShadowWidth(), getShadowHeight());
258             mFrame.draw(canvas);
259 
260             Bitmap bitmap = getOneThumbnail();
261             if (bitmap != null) {
262                 final View view = getView();
263                 canvas.drawBitmap(bitmap, view.getPaddingLeft(),
264                         view.getPaddingTop(), null);
265             }
266         }
267     }
268 
269     /**
270      * @return The shadow width
271      */
getShadowWidth()272     private int getShadowWidth() {
273         final int thumbnailHeight = getHeight() - getPaddingTop() - getPaddingBottom();
274         final int thumbnailWidth = (thumbnailHeight * mMediaItem.getWidth()) /
275             mMediaItem.getHeight();
276         return thumbnailWidth + getPaddingLeft() + getPaddingRight();
277     }
278 
279     /**
280      * @return The shadow height
281      */
getShadowHeight()282     private int getShadowHeight() {
283         return getHeight();
284     }
285 
getOneThumbnail()286     private Bitmap getOneThumbnail() {
287         ThumbnailKey key = new ThumbnailKey();
288         key.mediaItemId = mMediaItem.getId();
289 
290         // Find any one cached thumbnail
291         for (int i = 0; i < mNumberOfThumbnails; i++) {
292             key.index = i;
293             Bitmap bitmap = sThumbnailCache.get(key);
294             if (bitmap != null) {
295                 return bitmap;
296             }
297         }
298 
299         return null;
300     }
301 
302     /**
303      * @param projectPath The project path
304      */
setProjectPath(String projectPath)305     public void setProjectPath(String projectPath) {
306         mProjectPath = projectPath;
307     }
308 
309     /**
310      * @param listener The gesture listener
311      */
setGestureListener(ItemSimpleGestureListener listener)312     public void setGestureListener(ItemSimpleGestureListener listener) {
313         mGestureListener = listener;
314     }
315 
316     /**
317      * A view enters or exits the playback mode
318      *
319      * @param playback true if playback is in progress
320      */
setPlaybackMode(boolean playback)321     public void setPlaybackMode(boolean playback) {
322         mIsPlaying = playback;
323         invalidate();
324     }
325 
326     /**
327      * Resets the effect generation progress status.
328      */
resetGeneratingEffectProgress()329     public void resetGeneratingEffectProgress() {
330         setGeneratingEffectProgress(-1);
331     }
332 
333     /**
334      * Sets the effect generation progress of this view.
335      */
setGeneratingEffectProgress(int progress)336     public void setGeneratingEffectProgress(int progress) {
337         if (progress == 0) {
338             mGeneratingEffectProgress = progress;
339             // Release the current set of bitmaps. New content is being generated.
340             releaseBitmapsAndClear();
341         } else if (progress == 100) {
342             mGeneratingEffectProgress = -1;
343         } else {
344             mGeneratingEffectProgress = progress;
345         }
346 
347         invalidate();
348     }
349 
350     /**
351      * The view has been layout out.
352      *
353      * @param oldLeft The old left position
354      * @param oldRight The old right position
355      */
onLayoutPerformed(int oldLeft, int oldRight)356     public void onLayoutPerformed(int oldLeft, int oldRight) {
357         // Compute the thumbnail width and height
358         mThumbnailHeight = getHeight() - getPaddingTop() - getPaddingBottom();
359         mThumbnailWidth = (mThumbnailHeight * mMediaItem.getWidth()) / mMediaItem.getHeight();
360 
361         // We are not able to display a bitmap with width or height > 2048.
362         while (mThumbnailWidth > 2048 || mThumbnailHeight > 2048) {
363             mThumbnailHeight /= 2;
364             mThumbnailWidth /= 2;
365         }
366 
367         int usableWidth = getWidth() - getPaddingLeft() - getPaddingRight();
368         // Compute the ceiling of (usableWidth / mThumbnailWidth).
369         mNumberOfThumbnails = (usableWidth + mThumbnailWidth - 1) / mThumbnailWidth;
370         mBeginTimeMs = mMediaItem.getAppBoundaryBeginTime();
371         mEndTimeMs = mMediaItem.getAppBoundaryEndTime();
372 
373         releaseBitmapsAndClear();
374         invalidate();
375     }
376 
377     /**
378      * @return True if the effect generation is in progress
379      */
isGeneratingEffect()380     public boolean isGeneratingEffect() {
381         return (mGeneratingEffectProgress >= 0);
382     }
383 
setBitmap(Bitmap bitmap, int index, int token)384     public boolean setBitmap(Bitmap bitmap, int index, int token) {
385         // Ignore results from previous requests
386         if (token != mGeneration) {
387             return false;
388         }
389         if (!mPending.contains(index)) {
390             Log.e(TAG, "received unasked bitmap, index = " + index);
391             return false;
392         }
393         if (bitmap == null) {
394             Log.w(TAG, "receive null bitmap for index = " + index);
395             // We keep this request in mPending, so we won't request it again.
396             return false;
397         }
398         mPending.remove(index);
399         ThumbnailKey key = new ThumbnailKey(mMediaItem.getId(), index);
400         sThumbnailCache.put(key, bitmap);
401 
402         invalidate();
403         return true;
404     }
405 
406     @Override
onDraw(Canvas canvas)407     protected void onDraw(Canvas canvas) {
408         super.onDraw(canvas);
409         if (mGeneratingEffectProgress >= 0) {
410             ProgressBar.getProgressBar(getContext()).draw(
411                     canvas, mGeneratingEffectProgress, mGeneratingEffectProgressDestRect,
412                     getPaddingLeft(), getWidth() - getPaddingRight());
413         } else {
414             // Do not draw in the padding area
415             canvas.clipRect(getPaddingLeft(), getPaddingTop(),
416                     getWidth() - getPaddingRight(),
417                     getHeight() - getPaddingBottom());
418 
419             // Draw thumbnails
420             drawThumbnails(canvas);
421 
422             // Draw the "Add transition" indicators
423             if (isSelected()) {
424                 drawAddTransitionIcons(canvas);
425             } else if (mTimeline.hasItemSelected()) {
426                 // Dim myself if some view on the timeline is selected but not me
427                 // by drawing a transparent black overlay.
428                 final Paint paint = new Paint();
429                 paint.setColor(Color.BLACK);
430                 paint.setAlpha(192);
431                 canvas.drawPaint(paint);
432             }
433 
434             // Request thumbnails if things are not moving
435             boolean isBusy = mIsPlaying || mTimeline.isTrimming() || mIsScrolling;
436             if (!isBusy && !mWantThumbnails.isEmpty()) {
437                 requestThumbnails();
438             }
439         }
440     }
441 
442     // Draws the thumbnails, also put unavailable thumbnail indices in
443     // mWantThumbnails.
drawThumbnails(Canvas canvas)444     private void drawThumbnails(Canvas canvas) {
445         mWantThumbnails.clear();
446 
447         // The screen coordinate of the left edge of the usable area.
448         int left = getLeft() + getPaddingLeft() - mScrollX;
449         // The screen coordinate of the right edge of the usable area.
450         int right = getRight() - getPaddingRight() - mScrollX;
451         // Return if the usable area is not on screen.
452         if (left >= mScreenWidth || right <= 0 || left >= right) {
453             return;
454         }
455 
456         // Map [0, mScreenWidth - 1] to the indices of the thumbnail.
457         int startIdx = (0 - left) / mThumbnailWidth;
458         int endIdx = (mScreenWidth - 1 - left) / mThumbnailWidth;
459 
460         startIdx = clamp(startIdx, 0, mNumberOfThumbnails - 1);
461         endIdx = clamp(endIdx, 0, mNumberOfThumbnails - 1);
462 
463         // Prepare variables used in the loop
464         ThumbnailKey key = new ThumbnailKey();
465         key.mediaItemId = mMediaItem.getId();
466         int x = getPaddingLeft() + startIdx * mThumbnailWidth;
467         int y = getPaddingTop();
468 
469         // Center the thumbnail vertically
470         int spacing = (getHeight() - getPaddingTop() - getPaddingBottom() -
471                 mThumbnailHeight) / 2;
472         y += spacing;
473 
474         // Loop through the thumbnails on screen and draw it
475         for (int i = startIdx; i <= endIdx; i++) {
476             key.index = i;
477             Bitmap bitmap = sThumbnailCache.get(key);
478             if (bitmap == null) {
479                 // Draw a frame placeholder
480                 sEmptyFrameDrawable.setBounds(
481                         x, y, x + mThumbnailWidth, y + mThumbnailHeight);
482                 sEmptyFrameDrawable.draw(canvas);
483                 if (!mPending.contains(i)) {
484                     mWantThumbnails.add(Integer.valueOf(i));
485                 }
486             } else {
487                 canvas.drawBitmap(bitmap, x, y, null);
488             }
489             x += mThumbnailWidth;
490         }
491     }
492 
493     /**
494      * Draws the "Add transition" icons at the beginning and end of the media item.
495      *
496      * @param canvas Canvas to be drawn
497      */
drawAddTransitionIcons(Canvas canvas)498     private void drawAddTransitionIcons(Canvas canvas) {
499         if (hasSpaceForAddTransitionIcons()) {
500             if (mMediaItem.getBeginTransition() == null) {
501                 sAddTransitionDrawable.setState(mLeftState);
502                 sAddTransitionDrawable.setBounds(getPaddingLeft(), getPaddingTop(),
503                         sAddTransitionDrawable.getIntrinsicWidth() + getPaddingLeft(),
504                         getPaddingTop() + sAddTransitionDrawable.getIntrinsicHeight());
505                 sAddTransitionDrawable.draw(canvas);
506             }
507 
508             if (mMediaItem.getEndTransition() == null) {
509                 sAddTransitionDrawable.setState(mRightState);
510                 sAddTransitionDrawable.setBounds(
511                         getWidth() - getPaddingRight() -
512                         sAddTransitionDrawable.getIntrinsicWidth(),
513                         getPaddingTop(), getWidth() - getPaddingRight(),
514                         getPaddingTop() + sAddTransitionDrawable.getIntrinsicHeight());
515                 sAddTransitionDrawable.draw(canvas);
516             }
517         }
518     }
519 
520     /**
521      * @return true if the visible area of this view is big enough to display
522      *      "add transition" icons on both sides; false otherwise.
523      */
hasSpaceForAddTransitionIcons()524     private boolean hasSpaceForAddTransitionIcons() {
525         if (mTimeline.isTrimming()) {
526             return false;
527         }
528 
529         return (getWidth() - getPaddingLeft() - getPaddingRight() >=
530                 2 * sAddTransitionDrawable.getIntrinsicWidth());
531     }
532 
533     /**
534      * Clamps the input value v to the range [low, high].
535      */
clamp(int v, int low, int high)536     private static int clamp(int v, int low, int high) {
537         return Math.min(Math.max(v, low), high);
538     }
539 
540     /**
541      * Requests the thumbnails in mWantThumbnails (which is filled by onDraw).
542      */
requestThumbnails()543     private void requestThumbnails() {
544         // Copy mWantThumbnails to an array
545         int indices[] = new int[mWantThumbnails.size()];
546         for (int i = 0; i < mWantThumbnails.size(); i++) {
547             indices[i] = mWantThumbnails.get(i);
548         }
549 
550         // Put them in the pending set
551         mPending.addAll(mWantThumbnails);
552 
553         ApiService.getMediaItemThumbnails(getContext(), mProjectPath,
554                 mMediaItem.getId(), mThumbnailWidth, mThumbnailHeight,
555                 mBeginTimeMs, mEndTimeMs, mNumberOfThumbnails, mGeneration,
556                 indices);
557     }
558 
559     @Override
onTouchEvent(MotionEvent ev)560     public boolean onTouchEvent(MotionEvent ev) {
561         // Let the gesture detector inspect all events.
562         mGestureDetector.onTouchEvent(ev);
563         super.onTouchEvent(ev);
564 
565         switch (ev.getAction()) {
566             case MotionEvent.ACTION_DOWN: {
567                 mLeftState = View.EMPTY_STATE_SET;
568                 mRightState = View.EMPTY_STATE_SET;
569                 if (isSelected() && hasSpaceForAddTransitionIcons()) {
570                     if (ev.getX() < sAddTransitionDrawable.getIntrinsicWidth() +
571                             getPaddingLeft()) {
572                         if (mMediaItem.getBeginTransition() == null) {
573                             mLeftState = View.PRESSED_WINDOW_FOCUSED_STATE_SET;
574                         }
575                     } else if (ev.getX() >= getWidth() - getPaddingRight() -
576                             sAddTransitionDrawable.getIntrinsicWidth()) {
577                         if (mMediaItem.getEndTransition() == null) {
578                             mRightState = View.PRESSED_WINDOW_FOCUSED_STATE_SET;
579                         }
580                     }
581                 }
582                 invalidate();
583                 break;
584             }
585 
586             case MotionEvent.ACTION_UP:
587             case MotionEvent.ACTION_CANCEL: {
588                 mRightState = View.EMPTY_STATE_SET;
589                 mLeftState = View.EMPTY_STATE_SET;
590                 invalidate();
591                 break;
592             }
593 
594             default: {
595                 break;
596             }
597         }
598 
599         return true;
600     }
601 
releaseBitmapsAndClear()602     private void releaseBitmapsAndClear() {
603         sThumbnailCache.clearForMediaItemId(mMediaItem.getId());
604         mPending.clear();
605         mGeneration = sGenerationCounter++;
606     }
607 }
608 
609 class ThumbnailKey {
610     public String mediaItemId;
611     public int index;
612 
ThumbnailKey()613     public ThumbnailKey() {
614     }
615 
ThumbnailKey(String id, int idx)616     public ThumbnailKey(String id, int idx) {
617         mediaItemId = id;
618         index = idx;
619     }
620 
621     @Override
equals(Object o)622     public boolean equals(Object o) {
623         if (!(o instanceof ThumbnailKey)) {
624             return false;
625         }
626         ThumbnailKey key = (ThumbnailKey) o;
627         return index == key.index && mediaItemId.equals(key.mediaItemId);
628     }
629 
630     @Override
hashCode()631     public int hashCode() {
632         return mediaItemId.hashCode() ^ index;
633     }
634 }
635 
636 class ThumbnailCache {
637     private LruCache<ThumbnailKey, Bitmap> mCache;
638 
ThumbnailCache(int size)639     public ThumbnailCache(int size) {
640         mCache = new LruCache<ThumbnailKey, Bitmap>(size) {
641             @Override
642             protected int sizeOf(ThumbnailKey key, Bitmap value) {
643                 return value.getByteCount();
644             }
645         };
646     }
647 
put(ThumbnailKey key, Bitmap value)648     void put(ThumbnailKey key, Bitmap value) {
649         mCache.put(key, value);
650     }
651 
get(ThumbnailKey key)652     Bitmap get(ThumbnailKey key) {
653         return mCache.get(key);
654     }
655 
clearForMediaItemId(String id)656     void clearForMediaItemId(String id) {
657         Map<ThumbnailKey, Bitmap> map = mCache.snapshot();
658         for (ThumbnailKey key : map.keySet()) {
659             if (key.mediaItemId.equals(id)) {
660                 mCache.remove(key);
661             }
662         }
663     }
664 }
665