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