1 /* 2 * Copyright (C) 2014 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.tv.settings.util; 18 19 import com.android.tv.settings.widget.BitmapWorkerOptions; 20 import com.android.tv.settings.widget.DrawableDownloader; 21 22 import android.content.Context; 23 import android.content.Intent; 24 import android.graphics.Color; 25 import android.graphics.Rect; 26 import android.graphics.RectF; 27 import android.graphics.drawable.BitmapDrawable; 28 import android.graphics.drawable.Drawable; 29 import android.net.Uri; 30 import android.os.Bundle; 31 import android.os.Parcelable; 32 import android.text.TextUtils; 33 import android.view.View; 34 import android.widget.ImageView; 35 36 import java.util.ArrayList; 37 import java.util.List; 38 39 /** 40 * Initiator calls {@link #createFromImageView(ImageView)} and 41 * {@link #writeMultipleToIntent(List, Intent)}. 42 * <p> 43 * Receiver calls {@link #readMultipleFromIntent(Context, Intent)} to read source and 44 * {@link #createFromImageView(ImageView)} for target image view; then start animation 45 * using {@link TransitionImageView} between these two.<p> 46 * The matching of Uri is up to receiver, typically using {@link TransitionImageMatcher} 47 * <p> 48 * The transition image has three bounds, all relative to window<p> 49 * - {@link #setRect(Rect)} bounds of the image view, including background color<p> 50 * - {@link #setUnclippedRect(RectF)} bounds of original bitmap without clipping, the rect 51 * might be bigger than the image view<p> 52 * - {@link #setClippedRect(RectF)} bounds of clipping<p> 53 */ 54 public class TransitionImage { 55 56 private Uri mUri; 57 private BitmapDrawable mBitmap; 58 private final Rect mRect = new Rect(); 59 private int mBackground = Color.TRANSPARENT; 60 private float mAlpha = 1f; 61 private float mSaturation = 1f; 62 private RectF mUnclippedRect = new RectF(); 63 private RectF mClippedRect = new RectF(); 64 private Object mUserObject; 65 private boolean mUseClippedRectOnTransparent = true; 66 67 public static final String EXTRA_TRANSITION_BITMAP = 68 "com.android.tv.settings.transition_bitmap"; 69 public static final String EXTRA_TRANSITION_BITMAP_RECT = 70 "com.android.tv.settings.transition_bmp_rect"; 71 public static final String EXTRA_TRANSITION_BITMAP_URI = 72 "com.android.tv.settings.transition_bmp_uri"; 73 public static final String EXTRA_TRANSITION_BITMAP_ALPHA = 74 "com.android.tv.settings.transition_bmp_alpha"; 75 public static final String EXTRA_TRANSITION_BITMAP_SATURATION = 76 "com.android.tv.settings.transition_bmp_saturation"; 77 public static final String EXTRA_TRANSITION_BITMAP_BACKGROUND = 78 "com.android.tv.settings.transition_bmp_background"; 79 public static final String EXTRA_TRANSITION_BITMAP_UNCLIPPED_RECT = 80 "com.android.tv.settings.transition_bmp_unclipped_rect"; 81 public static final String EXTRA_TRANSITION_BITMAP_CLIPPED_RECT = 82 "com.android.tv.settings.transition_bmp_clipped_rect"; 83 public static final String EXTRA_TRANSITION_MULTIPLE_BITMAP = 84 "com.android.tv.settings.transition_multiple_bitmap"; 85 TransitionImage()86 public TransitionImage() { 87 } 88 getUri()89 public Uri getUri() { 90 return mUri; 91 } 92 setUri(Uri uri)93 public void setUri(Uri uri) { 94 mUri = uri; 95 } 96 getBitmap()97 public BitmapDrawable getBitmap() { 98 return mBitmap; 99 } 100 setBitmap(BitmapDrawable bitmap)101 public void setBitmap(BitmapDrawable bitmap) { 102 mBitmap = bitmap; 103 } 104 getRect()105 public Rect getRect() { 106 return mRect; 107 } 108 setRect(Rect rect)109 public void setRect(Rect rect) { 110 mRect.set(rect); 111 } 112 getBackground()113 public int getBackground() { 114 return mBackground; 115 } 116 setBackground(int color)117 public void setBackground(int color) { 118 mBackground = color; 119 } 120 getAlpha()121 public float getAlpha() { 122 return mAlpha; 123 } 124 setAlpha(float alpha)125 public void setAlpha(float alpha) { 126 mAlpha = alpha; 127 } 128 getSaturation()129 public float getSaturation() { 130 return mSaturation; 131 } 132 setSaturation(float saturation)133 public void setSaturation(float saturation) { 134 mSaturation = saturation; 135 } 136 getUnclippedRect()137 public RectF getUnclippedRect() { 138 return mUnclippedRect; 139 } 140 setUnclippedRect(RectF rect)141 public void setUnclippedRect(RectF rect) { 142 mUnclippedRect.set(rect); 143 } 144 getClippedRect()145 public RectF getClippedRect() { 146 return mClippedRect; 147 } 148 setClippedRect(RectF rect)149 public void setClippedRect(RectF rect) { 150 mClippedRect.set(rect); 151 } 152 readMultipleFromIntent(Context context, Intent intent)153 public static List<TransitionImage> readMultipleFromIntent(Context context, Intent intent) { 154 ArrayList<TransitionImage> transitions = new ArrayList<TransitionImage>(); 155 Bundle extras = intent.getExtras(); 156 if (extras == null) { 157 return transitions; 158 } 159 TransitionImage image = new TransitionImage(); 160 if (image.readFromBundle(context, intent.getSourceBounds(), extras)) { 161 transitions.add(image); 162 } 163 Parcelable[] multiple = 164 intent.getParcelableArrayExtra(EXTRA_TRANSITION_MULTIPLE_BITMAP); 165 if (multiple != null) { 166 for (int i = 0, size = multiple.length; i < size; i++) { 167 if (!(multiple[i] instanceof Bundle)) { 168 break; 169 } 170 image = new TransitionImage(); 171 if (image.readFromBundle(context, null, (Bundle) multiple[i])) { 172 transitions.add(image); 173 } 174 } 175 } 176 return transitions; 177 } 178 writeMultipleToIntent(List<TransitionImage> transitions, Intent intent)179 public static void writeMultipleToIntent(List<TransitionImage> transitions, Intent intent) { 180 if (transitions == null || transitions.size() == 0) { 181 return; 182 } 183 int size = transitions.size(); 184 if (size == 1) { 185 TransitionImage image = transitions.get(0); 186 image.writeToIntent(intent); 187 return; 188 } 189 Parcelable[] multipleBundle = new Parcelable[size]; 190 for (int i = 0; i < size; i++) { 191 Bundle b = new Bundle(); 192 transitions.get(i).writeToBundle(b); 193 multipleBundle[i] = b; 194 } 195 intent.putExtra(EXTRA_TRANSITION_MULTIPLE_BITMAP, multipleBundle); 196 } 197 readFromBundle(Context context, Rect intentSourceBounds, Bundle bundle)198 public boolean readFromBundle(Context context, Rect intentSourceBounds, Bundle bundle) { 199 setBitmap(null); 200 if (bundle == null) { 201 return false; 202 } 203 mUri = bundle.getParcelable(EXTRA_TRANSITION_BITMAP_URI); 204 BitmapDrawable bitmap = null; 205 if (mUri != null) { 206 DrawableDownloader downloader = DrawableDownloader.getInstance(context); 207 BitmapWorkerOptions key = new BitmapWorkerOptions.Builder(context) 208 .resource(mUri).build(); 209 Drawable d = downloader.getLargestBitmapFromMemCache(key); 210 if (d instanceof BitmapDrawable) { 211 bitmap = ((BitmapDrawable) d); 212 } 213 } 214 if (bitmap == null) { 215 if (bundle.containsKey(EXTRA_TRANSITION_BITMAP)) { 216 bitmap = new BitmapDrawable(context.getResources(), 217 ActivityTransitionBitmapHelper.getBitmapFromBinderBundle( 218 bundle.getBundle(EXTRA_TRANSITION_BITMAP))); 219 } 220 if (bitmap == null) { 221 return false; 222 } 223 } 224 Rect rect = null; 225 String bitmapRectStr = bundle.getString(EXTRA_TRANSITION_BITMAP_RECT); 226 if (!TextUtils.isEmpty(bitmapRectStr)) { 227 rect = Rect.unflattenFromString(bitmapRectStr); 228 } 229 if (rect == null) { 230 rect = intentSourceBounds; 231 } 232 if (rect == null) { 233 return false; 234 } 235 setBitmap(bitmap); 236 setRect(rect); 237 if (!readRectF(bundle.getFloatArray(EXTRA_TRANSITION_BITMAP_CLIPPED_RECT), 238 mClippedRect)) { 239 mClippedRect.set(rect); 240 } 241 if (!readRectF(bundle.getFloatArray(EXTRA_TRANSITION_BITMAP_UNCLIPPED_RECT), 242 mUnclippedRect)) { 243 mUnclippedRect.set(rect); 244 } 245 setAlpha(bundle.getFloat(EXTRA_TRANSITION_BITMAP_ALPHA, 1f)); 246 setSaturation(bundle.getFloat(EXTRA_TRANSITION_BITMAP_SATURATION, 1f)); 247 setBackground(bundle.getInt(EXTRA_TRANSITION_BITMAP_BACKGROUND, 0)); 248 return true; 249 } 250 writeToBundle(Bundle bundle)251 public void writeToBundle(Bundle bundle) { 252 bundle.putParcelable(EXTRA_TRANSITION_BITMAP_URI, mUri); 253 bundle.putString(EXTRA_TRANSITION_BITMAP_RECT, mRect.flattenToString()); 254 if (mBitmap != null) { 255 bundle.putBundle(EXTRA_TRANSITION_BITMAP, 256 ActivityTransitionBitmapHelper.bitmapAsBinderBundle(mBitmap.getBitmap())); 257 } 258 bundle.putFloatArray(EXTRA_TRANSITION_BITMAP_CLIPPED_RECT, 259 writeRectF(mClippedRect, new float[4])); 260 bundle.putFloatArray(EXTRA_TRANSITION_BITMAP_UNCLIPPED_RECT, 261 writeRectF(mUnclippedRect, new float[4])); 262 bundle.putFloat(EXTRA_TRANSITION_BITMAP_ALPHA, mAlpha); 263 bundle.putFloat(EXTRA_TRANSITION_BITMAP_SATURATION, mSaturation); 264 bundle.putInt(EXTRA_TRANSITION_BITMAP_BACKGROUND, mBackground); 265 } 266 writeToIntent(Intent intent)267 public void writeToIntent(Intent intent) { 268 intent.setSourceBounds(mRect); 269 intent.putExtra(EXTRA_TRANSITION_BITMAP_URI, mUri); 270 intent.putExtra(EXTRA_TRANSITION_BITMAP_RECT, mRect.flattenToString()); 271 if (mBitmap != null) { 272 intent.putExtra(EXTRA_TRANSITION_BITMAP, 273 ActivityTransitionBitmapHelper.bitmapAsBinderBundle(mBitmap.getBitmap())); 274 } 275 intent.putExtra(EXTRA_TRANSITION_BITMAP_CLIPPED_RECT, 276 writeRectF(mClippedRect, new float[4])); 277 intent.putExtra(EXTRA_TRANSITION_BITMAP_UNCLIPPED_RECT, 278 writeRectF(mUnclippedRect, new float[4])); 279 intent.putExtra(EXTRA_TRANSITION_BITMAP_ALPHA, mAlpha); 280 intent.putExtra(EXTRA_TRANSITION_BITMAP_SATURATION, mSaturation); 281 intent.putExtra(EXTRA_TRANSITION_BITMAP_BACKGROUND, mBackground); 282 } 283 readRectF(float[] values, RectF f)284 public static boolean readRectF(float[] values, RectF f) { 285 if (values == null || values.length != 4) { 286 return false; 287 } 288 f.set(values[0], values[1], values[2], values[3]); 289 return true; 290 } 291 writeRectF(RectF f, float[] values)292 public static float[] writeRectF(RectF f, float[] values) { 293 values[0] = f.left; 294 values[1] = f.top; 295 values[2] = f.right; 296 values[3] = f.bottom; 297 return values; 298 } 299 300 /** 301 * set bounds and bitmap 302 */ createFromImageView(ImageView imageView)303 public void createFromImageView(ImageView imageView) { 304 createFromImageView(imageView, imageView); 305 } 306 307 /** 308 * set bounds and bitmap 309 * 310 * @param backgroundView background view can be larger than the image view that will 311 * be drawn with background color 312 */ createFromImageView(ImageView view, View backgroundView)313 public void createFromImageView(ImageView view, View backgroundView) { 314 Drawable drawable = view.getDrawable(); 315 if (drawable instanceof BitmapDrawable) { 316 setBitmap((BitmapDrawable) drawable); 317 } 318 // use background View as the outside bounds and we can fill 319 // background color in it 320 mClippedRect.set(0, 0, backgroundView.getWidth(), backgroundView.getHeight()); 321 WindowLocationUtil.getLocationsInWindow(backgroundView, mClippedRect); 322 mClippedRect.round(mRect); 323 // get image view rects 324 WindowLocationUtil.getImageLocationsInWindow(view, mClippedRect, mUnclippedRect); 325 } 326 327 /** 328 * set if background is transparent, set if we want to use {@link #setClippedRect(RectF)} 329 * instead of {@link #setRect(Rect)}. Default value is true, and the value is not 330 * serialized. User should call it before using TransitionImageAnimation. 331 */ setUseClippedRectOnTransparent(boolean ignoreBackground)332 public void setUseClippedRectOnTransparent(boolean ignoreBackground) { 333 mUseClippedRectOnTransparent = ignoreBackground; 334 } 335 336 /** 337 * get if background is not transparent, set if we want to use {@link #setClippedRect(RectF)} 338 * instead of {@link #setRect(Rect)} 339 */ getUseClippedRectOnTransparent()340 public boolean getUseClippedRectOnTransparent() { 341 return mUseClippedRectOnTransparent; 342 } 343 344 /** 345 * Get optimized rect depending on the background color 346 */ getOptimizedRect(Rect rect)347 public void getOptimizedRect(Rect rect) { 348 if (mUseClippedRectOnTransparent && mBackground == Color.TRANSPARENT) { 349 mClippedRect.round(rect); 350 } else { 351 rect.set(mRect); 352 } 353 } 354 setUserObject(Object object)355 public void setUserObject(Object object) { 356 mUserObject = object; 357 } 358 getUserObject()359 public Object getUserObject() { 360 return mUserObject; 361 } 362 363 @Override toString()364 public String toString() { 365 return "{TransitionImage Uri=" + mUri + " rect=" + mRect 366 + " unclipRect=" + mUnclippedRect + " clipRect=" + mClippedRect 367 + " bitmap=" + mBitmap + " alpha=" + mAlpha + " saturation=" + mSaturation 368 + " background=" + mBackground; 369 } 370 } 371