1 /* 2 * Copyright (C) 2013 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.launcher3; 18 19 import android.animation.LayoutTransition; 20 import android.annotation.TargetApi; 21 import android.app.ActionBar; 22 import android.app.Activity; 23 import android.app.WallpaperInfo; 24 import android.app.WallpaperManager; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.pm.ApplicationInfo; 28 import android.content.pm.PackageManager; 29 import android.content.res.Resources; 30 import android.database.Cursor; 31 import android.database.DataSetObserver; 32 import android.graphics.Bitmap; 33 import android.graphics.BitmapFactory; 34 import android.graphics.Canvas; 35 import android.graphics.Matrix; 36 import android.graphics.Point; 37 import android.graphics.PorterDuff; 38 import android.graphics.Rect; 39 import android.graphics.RectF; 40 import android.graphics.drawable.BitmapDrawable; 41 import android.graphics.drawable.Drawable; 42 import android.graphics.drawable.LevelListDrawable; 43 import android.net.Uri; 44 import android.os.AsyncTask; 45 import android.os.Build; 46 import android.os.Bundle; 47 import android.provider.MediaStore; 48 import android.util.Log; 49 import android.util.Pair; 50 import android.view.ActionMode; 51 import android.view.LayoutInflater; 52 import android.view.Menu; 53 import android.view.MenuInflater; 54 import android.view.MenuItem; 55 import android.view.View; 56 import android.view.View.OnClickListener; 57 import android.view.View.OnLayoutChangeListener; 58 import android.view.ViewGroup; 59 import android.view.ViewPropertyAnimator; 60 import android.view.ViewTreeObserver; 61 import android.view.ViewTreeObserver.OnGlobalLayoutListener; 62 import android.view.WindowManager; 63 import android.view.animation.AccelerateInterpolator; 64 import android.view.animation.DecelerateInterpolator; 65 import android.widget.ArrayAdapter; 66 import android.widget.BaseAdapter; 67 import android.widget.FrameLayout; 68 import android.widget.HorizontalScrollView; 69 import android.widget.ImageView; 70 import android.widget.LinearLayout; 71 import android.widget.Toast; 72 73 import com.android.photos.BitmapRegionTileSource; 74 import com.android.photos.BitmapRegionTileSource.BitmapSource; 75 76 import java.io.File; 77 import java.io.FileOutputStream; 78 import java.io.IOException; 79 import java.util.ArrayList; 80 81 public class WallpaperPickerActivity extends WallpaperCropActivity { 82 static final String TAG = "Launcher.WallpaperPickerActivity"; 83 84 public static final int IMAGE_PICK = 5; 85 public static final int PICK_WALLPAPER_THIRD_PARTY_ACTIVITY = 6; 86 public static final int PICK_LIVE_WALLPAPER = 7; 87 private static final String TEMP_WALLPAPER_TILES = "TEMP_WALLPAPER_TILES"; 88 private static final String SELECTED_INDEX = "SELECTED_INDEX"; 89 private static final int FLAG_POST_DELAY_MILLIS = 200; 90 91 private View mSelectedTile; 92 private boolean mIgnoreNextTap; 93 private OnClickListener mThumbnailOnClickListener; 94 95 private LinearLayout mWallpapersView; 96 private View mWallpaperStrip; 97 98 private ActionMode.Callback mActionModeCallback; 99 private ActionMode mActionMode; 100 101 private View.OnLongClickListener mLongClickListener; 102 103 ArrayList<Uri> mTempWallpaperTiles = new ArrayList<Uri>(); 104 private SavedWallpaperImages mSavedImages; 105 private WallpaperInfo mLiveWallpaperInfoOnPickerLaunch; 106 private int mSelectedIndex = -1; 107 private WallpaperInfo mLastClickedLiveWallpaperInfo; 108 109 public static abstract class WallpaperTileInfo { 110 protected View mView; 111 public Drawable mThumb; 112 setView(View v)113 public void setView(View v) { 114 mView = v; 115 } onClick(WallpaperPickerActivity a)116 public void onClick(WallpaperPickerActivity a) {} onSave(WallpaperPickerActivity a)117 public void onSave(WallpaperPickerActivity a) {} onDelete(WallpaperPickerActivity a)118 public void onDelete(WallpaperPickerActivity a) {} isSelectable()119 public boolean isSelectable() { return false; } isNamelessWallpaper()120 public boolean isNamelessWallpaper() { return false; } onIndexUpdated(CharSequence label)121 public void onIndexUpdated(CharSequence label) { 122 if (isNamelessWallpaper()) { 123 mView.setContentDescription(label); 124 } 125 } 126 } 127 128 public static class PickImageInfo extends WallpaperTileInfo { 129 @Override onClick(WallpaperPickerActivity a)130 public void onClick(WallpaperPickerActivity a) { 131 Intent intent = new Intent(Intent.ACTION_GET_CONTENT); 132 intent.setType("image/*"); 133 a.startActivityForResultSafely(intent, IMAGE_PICK); 134 } 135 } 136 137 public static class UriWallpaperInfo extends WallpaperTileInfo { 138 private Uri mUri; 139 private boolean mFirstClick = true; 140 private BitmapRegionTileSource.UriBitmapSource mBitmapSource; UriWallpaperInfo(Uri uri)141 public UriWallpaperInfo(Uri uri) { 142 mUri = uri; 143 } 144 @Override onClick(final WallpaperPickerActivity a)145 public void onClick(final WallpaperPickerActivity a) { 146 final Runnable onLoad; 147 if (!mFirstClick) { 148 onLoad = null; 149 } else { 150 mFirstClick = false; 151 a.mSetWallpaperButton.setEnabled(false); 152 onLoad = new Runnable() { 153 public void run() { 154 if (mBitmapSource != null && 155 mBitmapSource.getLoadingState() == BitmapSource.State.LOADED) { 156 a.selectTile(mView); 157 a.mSetWallpaperButton.setEnabled(true); 158 } else { 159 ViewGroup parent = (ViewGroup) mView.getParent(); 160 if (parent != null) { 161 parent.removeView(mView); 162 Toast.makeText(a, 163 a.getString(R.string.image_load_fail), 164 Toast.LENGTH_SHORT).show(); 165 } 166 } 167 } 168 }; 169 } 170 mBitmapSource = new BitmapRegionTileSource.UriBitmapSource( 171 a, mUri, BitmapRegionTileSource.MAX_PREVIEW_SIZE); 172 a.setCropViewTileSource(mBitmapSource, true, false, onLoad); 173 } 174 @Override onSave(final WallpaperPickerActivity a)175 public void onSave(final WallpaperPickerActivity a) { 176 boolean finishActivityWhenDone = true; 177 OnBitmapCroppedHandler h = new OnBitmapCroppedHandler() { 178 public void onBitmapCropped(byte[] imageBytes) { 179 Point thumbSize = getDefaultThumbnailSize(a.getResources()); 180 // rotation is set to 0 since imageBytes has already been correctly rotated 181 Bitmap thumb = createThumbnail( 182 thumbSize, null, null, imageBytes, null, 0, 0, true); 183 a.getSavedImages().writeImage(thumb, imageBytes); 184 } 185 }; 186 a.cropImageAndSetWallpaper(mUri, h, finishActivityWhenDone); 187 } 188 @Override isSelectable()189 public boolean isSelectable() { 190 return true; 191 } 192 @Override isNamelessWallpaper()193 public boolean isNamelessWallpaper() { 194 return true; 195 } 196 } 197 198 public static class FileWallpaperInfo extends WallpaperTileInfo { 199 private File mFile; 200 FileWallpaperInfo(File target, Drawable thumb)201 public FileWallpaperInfo(File target, Drawable thumb) { 202 mFile = target; 203 mThumb = thumb; 204 } 205 @Override onClick(WallpaperPickerActivity a)206 public void onClick(WallpaperPickerActivity a) { 207 BitmapRegionTileSource.UriBitmapSource bitmapSource = 208 new BitmapRegionTileSource.UriBitmapSource(a, Uri.fromFile(mFile), 1024); 209 a.setCropViewTileSource(bitmapSource, false, true, null); 210 } 211 @Override onSave(WallpaperPickerActivity a)212 public void onSave(WallpaperPickerActivity a) { 213 a.setWallpaper(Uri.fromFile(mFile), true); 214 } 215 @Override isSelectable()216 public boolean isSelectable() { 217 return true; 218 } 219 @Override isNamelessWallpaper()220 public boolean isNamelessWallpaper() { 221 return true; 222 } 223 } 224 225 public static class ResourceWallpaperInfo extends WallpaperTileInfo { 226 private Resources mResources; 227 private int mResId; 228 ResourceWallpaperInfo(Resources res, int resId, Drawable thumb)229 public ResourceWallpaperInfo(Resources res, int resId, Drawable thumb) { 230 mResources = res; 231 mResId = resId; 232 mThumb = thumb; 233 } 234 @Override onClick(WallpaperPickerActivity a)235 public void onClick(WallpaperPickerActivity a) { 236 BitmapRegionTileSource.ResourceBitmapSource bitmapSource = 237 new BitmapRegionTileSource.ResourceBitmapSource( 238 mResources, mResId, BitmapRegionTileSource.MAX_PREVIEW_SIZE); 239 bitmapSource.loadInBackground(); 240 BitmapRegionTileSource source = new BitmapRegionTileSource(a, bitmapSource); 241 CropView v = a.getCropView(); 242 v.setTileSource(source, null); 243 Point wallpaperSize = WallpaperCropActivity.getDefaultWallpaperSize( 244 a.getResources(), a.getWindowManager()); 245 RectF crop = WallpaperCropActivity.getMaxCropRect( 246 source.getImageWidth(), source.getImageHeight(), 247 wallpaperSize.x, wallpaperSize.y, false); 248 v.setScale(wallpaperSize.x / crop.width()); 249 v.setTouchEnabled(false); 250 a.setSystemWallpaperVisiblity(false); 251 } 252 @Override onSave(WallpaperPickerActivity a)253 public void onSave(WallpaperPickerActivity a) { 254 boolean finishActivityWhenDone = true; 255 a.cropImageAndSetWallpaper(mResources, mResId, finishActivityWhenDone); 256 } 257 @Override isSelectable()258 public boolean isSelectable() { 259 return true; 260 } 261 @Override isNamelessWallpaper()262 public boolean isNamelessWallpaper() { 263 return true; 264 } 265 } 266 267 @TargetApi(Build.VERSION_CODES.KITKAT) 268 public static class DefaultWallpaperInfo extends WallpaperTileInfo { DefaultWallpaperInfo(Drawable thumb)269 public DefaultWallpaperInfo(Drawable thumb) { 270 mThumb = thumb; 271 } 272 @Override onClick(WallpaperPickerActivity a)273 public void onClick(WallpaperPickerActivity a) { 274 CropView c = a.getCropView(); 275 276 Drawable defaultWallpaper = WallpaperManager.getInstance(a).getBuiltInDrawable( 277 c.getWidth(), c.getHeight(), false, 0.5f, 0.5f); 278 279 if (defaultWallpaper == null) { 280 Log.w(TAG, "Null default wallpaper encountered."); 281 c.setTileSource(null, null); 282 return; 283 } 284 285 c.setTileSource( 286 new DrawableTileSource(a, defaultWallpaper, DrawableTileSource.MAX_PREVIEW_SIZE), null); 287 c.setScale(1f); 288 c.setTouchEnabled(false); 289 a.setSystemWallpaperVisiblity(false); 290 } 291 @Override onSave(WallpaperPickerActivity a)292 public void onSave(WallpaperPickerActivity a) { 293 try { 294 WallpaperManager.getInstance(a).clear(); 295 a.setResult(RESULT_OK); 296 } catch (IOException e) { 297 Log.w("Setting wallpaper to default threw exception", e); 298 } 299 a.finish(); 300 } 301 @Override isSelectable()302 public boolean isSelectable() { 303 return true; 304 } 305 @Override isNamelessWallpaper()306 public boolean isNamelessWallpaper() { 307 return true; 308 } 309 } 310 setWallpaperStripYOffset(float offset)311 public void setWallpaperStripYOffset(float offset) { 312 mWallpaperStrip.setPadding(0, 0, 0, (int) offset); 313 } 314 315 /** 316 * shows the system wallpaper behind the window and hides the {@link 317 * #mCropView} if visible 318 * @param visible should the system wallpaper be shown 319 */ setSystemWallpaperVisiblity(final boolean visible)320 protected void setSystemWallpaperVisiblity(final boolean visible) { 321 // hide our own wallpaper preview if necessary 322 if(!visible) { 323 mCropView.setVisibility(View.VISIBLE); 324 } else { 325 changeWallpaperFlags(visible); 326 } 327 // the change of the flag must be delayed in order to avoid flickering, 328 // a simple post / double post does not suffice here 329 mCropView.postDelayed(new Runnable() { 330 @Override 331 public void run() { 332 if(!visible) { 333 changeWallpaperFlags(visible); 334 } else { 335 mCropView.setVisibility(View.INVISIBLE); 336 } 337 } 338 }, FLAG_POST_DELAY_MILLIS); 339 } 340 changeWallpaperFlags(boolean visible)341 private void changeWallpaperFlags(boolean visible) { 342 int desiredWallpaperFlag = visible ? WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER : 0; 343 int currentWallpaperFlag = getWindow().getAttributes().flags 344 & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; 345 if (desiredWallpaperFlag != currentWallpaperFlag) { 346 getWindow().setFlags(desiredWallpaperFlag, 347 WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER); 348 } 349 } 350 351 @Override setCropViewTileSource(BitmapSource bitmapSource, boolean touchEnabled, boolean moveToLeft, final Runnable postExecute)352 public void setCropViewTileSource(BitmapSource bitmapSource, 353 boolean touchEnabled, 354 boolean moveToLeft, 355 final Runnable postExecute) { 356 // we also want to show our own wallpaper instead of the one in the background 357 Runnable showPostExecuteRunnable = new Runnable() { 358 @Override 359 public void run() { 360 if(postExecute != null) { 361 postExecute.run(); 362 } 363 setSystemWallpaperVisiblity(false); 364 } 365 }; 366 super.setCropViewTileSource(bitmapSource, 367 touchEnabled, 368 moveToLeft, 369 showPostExecuteRunnable); 370 } 371 372 // called by onCreate; this is subclassed to overwrite WallpaperCropActivity init()373 protected void init() { 374 setContentView(R.layout.wallpaper_picker); 375 376 mCropView = (CropView) findViewById(R.id.cropView); 377 mCropView.setVisibility(View.INVISIBLE); 378 379 mWallpaperStrip = findViewById(R.id.wallpaper_strip); 380 mCropView.setTouchCallback(new CropView.TouchCallback() { 381 ViewPropertyAnimator mAnim; 382 @Override 383 public void onTouchDown() { 384 if (mAnim != null) { 385 mAnim.cancel(); 386 } 387 if (mWallpaperStrip.getAlpha() == 1f) { 388 mIgnoreNextTap = true; 389 } 390 mAnim = mWallpaperStrip.animate(); 391 mAnim.alpha(0f) 392 .setDuration(150) 393 .withEndAction(new Runnable() { 394 public void run() { 395 mWallpaperStrip.setVisibility(View.INVISIBLE); 396 } 397 }); 398 mAnim.setInterpolator(new AccelerateInterpolator(0.75f)); 399 mAnim.start(); 400 } 401 @Override 402 public void onTouchUp() { 403 mIgnoreNextTap = false; 404 } 405 @Override 406 public void onTap() { 407 boolean ignoreTap = mIgnoreNextTap; 408 mIgnoreNextTap = false; 409 if (!ignoreTap) { 410 if (mAnim != null) { 411 mAnim.cancel(); 412 } 413 mWallpaperStrip.setVisibility(View.VISIBLE); 414 mAnim = mWallpaperStrip.animate(); 415 mAnim.alpha(1f) 416 .setDuration(150) 417 .setInterpolator(new DecelerateInterpolator(0.75f)); 418 mAnim.start(); 419 } 420 } 421 }); 422 423 mThumbnailOnClickListener = new OnClickListener() { 424 public void onClick(View v) { 425 if (mActionMode != null) { 426 // When CAB is up, clicking toggles the item instead 427 if (v.isLongClickable()) { 428 mLongClickListener.onLongClick(v); 429 } 430 return; 431 } 432 mSetWallpaperButton.setEnabled(true); 433 WallpaperTileInfo info = (WallpaperTileInfo) v.getTag(); 434 if (info.isSelectable() && v.getVisibility() == View.VISIBLE) { 435 selectTile(v); 436 } 437 info.onClick(WallpaperPickerActivity.this); 438 } 439 }; 440 mLongClickListener = new View.OnLongClickListener() { 441 // Called when the user long-clicks on someView 442 public boolean onLongClick(View view) { 443 CheckableFrameLayout c = (CheckableFrameLayout) view; 444 c.toggle(); 445 446 if (mActionMode != null) { 447 mActionMode.invalidate(); 448 } else { 449 // Start the CAB using the ActionMode.Callback defined below 450 mActionMode = startActionMode(mActionModeCallback); 451 int childCount = mWallpapersView.getChildCount(); 452 for (int i = 0; i < childCount; i++) { 453 mWallpapersView.getChildAt(i).setSelected(false); 454 } 455 } 456 return true; 457 } 458 }; 459 460 // Populate the built-in wallpapers 461 ArrayList<WallpaperTileInfo> wallpapers = findBundledWallpapers(); 462 mWallpapersView = (LinearLayout) findViewById(R.id.wallpaper_list); 463 SimpleWallpapersAdapter ia = new SimpleWallpapersAdapter(this, wallpapers); 464 populateWallpapersFromAdapter(mWallpapersView, ia, false); 465 466 // Populate the saved wallpapers 467 mSavedImages = new SavedWallpaperImages(this); 468 mSavedImages.loadThumbnailsAndImageIdList(); 469 populateWallpapersFromAdapter(mWallpapersView, mSavedImages, true); 470 471 // Populate the live wallpapers 472 final LinearLayout liveWallpapersView = 473 (LinearLayout) findViewById(R.id.live_wallpaper_list); 474 final LiveWallpaperListAdapter a = new LiveWallpaperListAdapter(this); 475 a.registerDataSetObserver(new DataSetObserver() { 476 public void onChanged() { 477 liveWallpapersView.removeAllViews(); 478 populateWallpapersFromAdapter(liveWallpapersView, a, false); 479 initializeScrollForRtl(); 480 updateTileIndices(); 481 } 482 }); 483 484 // Populate the third-party wallpaper pickers 485 final LinearLayout thirdPartyWallpapersView = 486 (LinearLayout) findViewById(R.id.third_party_wallpaper_list); 487 final ThirdPartyWallpaperPickerListAdapter ta = 488 new ThirdPartyWallpaperPickerListAdapter(this); 489 populateWallpapersFromAdapter(thirdPartyWallpapersView, ta, false); 490 491 // Add a tile for the Gallery 492 LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list); 493 FrameLayout pickImageTile = (FrameLayout) getLayoutInflater(). 494 inflate(R.layout.wallpaper_picker_image_picker_item, masterWallpaperList, false); 495 setWallpaperItemPaddingToZero(pickImageTile); 496 masterWallpaperList.addView(pickImageTile, 0); 497 498 // Make its background the last photo taken on external storage 499 Bitmap lastPhoto = getThumbnailOfLastPhoto(); 500 if (lastPhoto != null) { 501 ImageView galleryThumbnailBg = 502 (ImageView) pickImageTile.findViewById(R.id.wallpaper_image); 503 galleryThumbnailBg.setImageBitmap(getThumbnailOfLastPhoto()); 504 int colorOverlay = getResources().getColor(R.color.wallpaper_picker_translucent_gray); 505 galleryThumbnailBg.setColorFilter(colorOverlay, PorterDuff.Mode.SRC_ATOP); 506 507 } 508 509 PickImageInfo pickImageInfo = new PickImageInfo(); 510 pickImageTile.setTag(pickImageInfo); 511 pickImageInfo.setView(pickImageTile); 512 pickImageTile.setOnClickListener(mThumbnailOnClickListener); 513 514 // Select the first item; wait for a layout pass so that we initialize the dimensions of 515 // cropView or the defaultWallpaperView first 516 mCropView.addOnLayoutChangeListener(new OnLayoutChangeListener() { 517 @Override 518 public void onLayoutChange(View v, int left, int top, int right, int bottom, 519 int oldLeft, int oldTop, int oldRight, int oldBottom) { 520 if ((right - left) > 0 && (bottom - top) > 0) { 521 if (mSelectedIndex >= 0 && mSelectedIndex < mWallpapersView.getChildCount()) { 522 mThumbnailOnClickListener.onClick( 523 mWallpapersView.getChildAt(mSelectedIndex)); 524 setSystemWallpaperVisiblity(false); 525 } 526 v.removeOnLayoutChangeListener(this); 527 } 528 } 529 }); 530 531 updateTileIndices(); 532 533 // Update the scroll for RTL 534 initializeScrollForRtl(); 535 536 // Create smooth layout transitions for when items are deleted 537 final LayoutTransition transitioner = new LayoutTransition(); 538 transitioner.setDuration(200); 539 transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0); 540 transitioner.setAnimator(LayoutTransition.DISAPPEARING, null); 541 mWallpapersView.setLayoutTransition(transitioner); 542 543 // Action bar 544 // Show the custom action bar view 545 final ActionBar actionBar = getActionBar(); 546 actionBar.setCustomView(R.layout.actionbar_set_wallpaper); 547 actionBar.getCustomView().setOnClickListener( 548 new View.OnClickListener() { 549 @Override 550 public void onClick(View v) { 551 if (mSelectedTile != null) { 552 WallpaperTileInfo info = (WallpaperTileInfo) mSelectedTile.getTag(); 553 info.onSave(WallpaperPickerActivity.this); 554 } else { 555 // no tile was selected, so we just finish the activity and go back 556 setResult(Activity.RESULT_OK); 557 finish(); 558 } 559 } 560 }); 561 mSetWallpaperButton = findViewById(R.id.set_wallpaper_button); 562 563 // CAB for deleting items 564 mActionModeCallback = new ActionMode.Callback() { 565 // Called when the action mode is created; startActionMode() was called 566 @Override 567 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 568 // Inflate a menu resource providing context menu items 569 MenuInflater inflater = mode.getMenuInflater(); 570 inflater.inflate(R.menu.cab_delete_wallpapers, menu); 571 return true; 572 } 573 574 private int numCheckedItems() { 575 int childCount = mWallpapersView.getChildCount(); 576 int numCheckedItems = 0; 577 for (int i = 0; i < childCount; i++) { 578 CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i); 579 if (c.isChecked()) { 580 numCheckedItems++; 581 } 582 } 583 return numCheckedItems; 584 } 585 586 // Called each time the action mode is shown. Always called after onCreateActionMode, 587 // but may be called multiple times if the mode is invalidated. 588 @Override 589 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 590 int numCheckedItems = numCheckedItems(); 591 if (numCheckedItems == 0) { 592 mode.finish(); 593 return true; 594 } else { 595 mode.setTitle(getResources().getQuantityString( 596 R.plurals.number_of_items_selected, numCheckedItems, numCheckedItems)); 597 return true; 598 } 599 } 600 601 // Called when the user selects a contextual menu item 602 @Override 603 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 604 int itemId = item.getItemId(); 605 if (itemId == R.id.menu_delete) { 606 int childCount = mWallpapersView.getChildCount(); 607 ArrayList<View> viewsToRemove = new ArrayList<View>(); 608 boolean selectedTileRemoved = false; 609 for (int i = 0; i < childCount; i++) { 610 CheckableFrameLayout c = 611 (CheckableFrameLayout) mWallpapersView.getChildAt(i); 612 if (c.isChecked()) { 613 WallpaperTileInfo info = (WallpaperTileInfo) c.getTag(); 614 info.onDelete(WallpaperPickerActivity.this); 615 viewsToRemove.add(c); 616 if (i == mSelectedIndex) { 617 selectedTileRemoved = true; 618 } 619 } 620 } 621 for (View v : viewsToRemove) { 622 mWallpapersView.removeView(v); 623 } 624 if (selectedTileRemoved) { 625 mSelectedIndex = -1; 626 mSelectedTile = null; 627 setSystemWallpaperVisiblity(true); 628 } 629 updateTileIndices(); 630 mode.finish(); // Action picked, so close the CAB 631 return true; 632 } else { 633 return false; 634 } 635 } 636 637 // Called when the user exits the action mode 638 @Override 639 public void onDestroyActionMode(ActionMode mode) { 640 int childCount = mWallpapersView.getChildCount(); 641 for (int i = 0; i < childCount; i++) { 642 CheckableFrameLayout c = (CheckableFrameLayout) mWallpapersView.getChildAt(i); 643 c.setChecked(false); 644 } 645 if (mSelectedTile != null) { 646 mSelectedTile.setSelected(true); 647 } 648 mActionMode = null; 649 } 650 }; 651 } 652 selectTile(View v)653 private void selectTile(View v) { 654 if (mSelectedTile != null) { 655 mSelectedTile.setSelected(false); 656 mSelectedTile = null; 657 } 658 mSelectedTile = v; 659 v.setSelected(true); 660 mSelectedIndex = mWallpapersView.indexOfChild(v); 661 // TODO: Remove this once the accessibility framework and 662 // services have better support for selection state. 663 v.announceForAccessibility( 664 getString(R.string.announce_selection, v.getContentDescription())); 665 } 666 initializeScrollForRtl()667 private void initializeScrollForRtl() { 668 final HorizontalScrollView scroll = 669 (HorizontalScrollView) findViewById(R.id.wallpaper_scroll_container); 670 671 if (scroll.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { 672 final ViewTreeObserver observer = scroll.getViewTreeObserver(); 673 observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { 674 public void onGlobalLayout() { 675 LinearLayout masterWallpaperList = 676 (LinearLayout) findViewById(R.id.master_wallpaper_list); 677 scroll.scrollTo(masterWallpaperList.getWidth(), 0); 678 scroll.getViewTreeObserver().removeOnGlobalLayoutListener(this); 679 } 680 }); 681 } 682 } 683 getThumbnailOfLastPhoto()684 protected Bitmap getThumbnailOfLastPhoto() { 685 Cursor cursor = MediaStore.Images.Media.query(getContentResolver(), 686 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 687 new String[] { MediaStore.Images.ImageColumns._ID, 688 MediaStore.Images.ImageColumns.DATE_TAKEN}, 689 null, null, MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC LIMIT 1"); 690 691 Bitmap thumb = null; 692 if (cursor != null) { 693 if (cursor.moveToNext()) { 694 int id = cursor.getInt(0); 695 thumb = MediaStore.Images.Thumbnails.getThumbnail(getContentResolver(), 696 id, MediaStore.Images.Thumbnails.MINI_KIND, null); 697 } 698 cursor.close(); 699 } 700 return thumb; 701 } 702 onStop()703 protected void onStop() { 704 super.onStop(); 705 mWallpaperStrip = findViewById(R.id.wallpaper_strip); 706 if (mWallpaperStrip.getAlpha() < 1f) { 707 mWallpaperStrip.setAlpha(1f); 708 mWallpaperStrip.setVisibility(View.VISIBLE); 709 } 710 } 711 onSaveInstanceState(Bundle outState)712 protected void onSaveInstanceState(Bundle outState) { 713 outState.putParcelableArrayList(TEMP_WALLPAPER_TILES, mTempWallpaperTiles); 714 outState.putInt(SELECTED_INDEX, mSelectedIndex); 715 } 716 onRestoreInstanceState(Bundle savedInstanceState)717 protected void onRestoreInstanceState(Bundle savedInstanceState) { 718 ArrayList<Uri> uris = savedInstanceState.getParcelableArrayList(TEMP_WALLPAPER_TILES); 719 for (Uri uri : uris) { 720 addTemporaryWallpaperTile(uri, true); 721 } 722 mSelectedIndex = savedInstanceState.getInt(SELECTED_INDEX, -1); 723 } 724 populateWallpapersFromAdapter(ViewGroup parent, BaseAdapter adapter, boolean addLongPressHandler)725 private void populateWallpapersFromAdapter(ViewGroup parent, BaseAdapter adapter, 726 boolean addLongPressHandler) { 727 for (int i = 0; i < adapter.getCount(); i++) { 728 FrameLayout thumbnail = (FrameLayout) adapter.getView(i, null, parent); 729 parent.addView(thumbnail, i); 730 WallpaperTileInfo info = (WallpaperTileInfo) adapter.getItem(i); 731 thumbnail.setTag(info); 732 info.setView(thumbnail); 733 if (addLongPressHandler) { 734 addLongPressHandler(thumbnail); 735 } 736 thumbnail.setOnClickListener(mThumbnailOnClickListener); 737 } 738 } 739 updateTileIndices()740 private void updateTileIndices() { 741 LinearLayout masterWallpaperList = (LinearLayout) findViewById(R.id.master_wallpaper_list); 742 final int childCount = masterWallpaperList.getChildCount(); 743 final Resources res = getResources(); 744 745 // Do two passes; the first pass gets the total number of tiles 746 int numTiles = 0; 747 for (int passNum = 0; passNum < 2; passNum++) { 748 int tileIndex = 0; 749 for (int i = 0; i < childCount; i++) { 750 View child = masterWallpaperList.getChildAt(i); 751 LinearLayout subList; 752 753 int subListStart; 754 int subListEnd; 755 if (child.getTag() instanceof WallpaperTileInfo) { 756 subList = masterWallpaperList; 757 subListStart = i; 758 subListEnd = i + 1; 759 } else { // if (child instanceof LinearLayout) { 760 subList = (LinearLayout) child; 761 subListStart = 0; 762 subListEnd = subList.getChildCount(); 763 } 764 765 for (int j = subListStart; j < subListEnd; j++) { 766 WallpaperTileInfo info = (WallpaperTileInfo) subList.getChildAt(j).getTag(); 767 if (info.isNamelessWallpaper()) { 768 if (passNum == 0) { 769 numTiles++; 770 } else { 771 CharSequence label = res.getString( 772 R.string.wallpaper_accessibility_name, ++tileIndex, numTiles); 773 info.onIndexUpdated(label); 774 } 775 } 776 } 777 } 778 } 779 } 780 getDefaultThumbnailSize(Resources res)781 private static Point getDefaultThumbnailSize(Resources res) { 782 return new Point(res.getDimensionPixelSize(R.dimen.wallpaperThumbnailWidth), 783 res.getDimensionPixelSize(R.dimen.wallpaperThumbnailHeight)); 784 785 } 786 createThumbnail(Point size, Context context, Uri uri, byte[] imageBytes, Resources res, int resId, int rotation, boolean leftAligned)787 private static Bitmap createThumbnail(Point size, Context context, Uri uri, byte[] imageBytes, 788 Resources res, int resId, int rotation, boolean leftAligned) { 789 int width = size.x; 790 int height = size.y; 791 792 BitmapCropTask cropTask; 793 if (uri != null) { 794 cropTask = new BitmapCropTask( 795 context, uri, null, rotation, width, height, false, true, null); 796 } else if (imageBytes != null) { 797 cropTask = new BitmapCropTask( 798 imageBytes, null, rotation, width, height, false, true, null); 799 } else { 800 cropTask = new BitmapCropTask( 801 context, res, resId, null, rotation, width, height, false, true, null); 802 } 803 Point bounds = cropTask.getImageBounds(); 804 if (bounds == null || bounds.x == 0 || bounds.y == 0) { 805 return null; 806 } 807 808 Matrix rotateMatrix = new Matrix(); 809 rotateMatrix.setRotate(rotation); 810 float[] rotatedBounds = new float[] { bounds.x, bounds.y }; 811 rotateMatrix.mapPoints(rotatedBounds); 812 rotatedBounds[0] = Math.abs(rotatedBounds[0]); 813 rotatedBounds[1] = Math.abs(rotatedBounds[1]); 814 815 RectF cropRect = WallpaperCropActivity.getMaxCropRect( 816 (int) rotatedBounds[0], (int) rotatedBounds[1], width, height, leftAligned); 817 cropTask.setCropBounds(cropRect); 818 819 if (cropTask.cropBitmap()) { 820 return cropTask.getCroppedBitmap(); 821 } else { 822 return null; 823 } 824 } 825 addTemporaryWallpaperTile(final Uri uri, boolean fromRestore)826 private void addTemporaryWallpaperTile(final Uri uri, boolean fromRestore) { 827 mTempWallpaperTiles.add(uri); 828 // Add a tile for the image picked from Gallery 829 final FrameLayout pickedImageThumbnail = (FrameLayout) getLayoutInflater(). 830 inflate(R.layout.wallpaper_picker_item, mWallpapersView, false); 831 pickedImageThumbnail.setVisibility(View.GONE); 832 setWallpaperItemPaddingToZero(pickedImageThumbnail); 833 mWallpapersView.addView(pickedImageThumbnail, 0); 834 835 // Load the thumbnail 836 final ImageView image = (ImageView) pickedImageThumbnail.findViewById(R.id.wallpaper_image); 837 final Point defaultSize = getDefaultThumbnailSize(this.getResources()); 838 final Context context = this; 839 new AsyncTask<Void, Bitmap, Bitmap>() { 840 protected Bitmap doInBackground(Void...args) { 841 try { 842 int rotation = WallpaperCropActivity.getRotationFromExif(context, uri); 843 return createThumbnail(defaultSize, context, uri, null, null, 0, rotation, false); 844 } catch (SecurityException securityException) { 845 if (isDestroyed()) { 846 // Temporarily granted permissions are revoked when the activity 847 // finishes, potentially resulting in a SecurityException here. 848 // Even though {@link #isDestroyed} might also return true in different 849 // situations where the configuration changes, we are fine with 850 // catching these cases here as well. 851 cancel(false); 852 } else { 853 // otherwise it had a different cause and we throw it further 854 throw securityException; 855 } 856 return null; 857 } 858 } 859 protected void onPostExecute(Bitmap thumb) { 860 if (!isCancelled() && thumb != null) { 861 image.setImageBitmap(thumb); 862 Drawable thumbDrawable = image.getDrawable(); 863 thumbDrawable.setDither(true); 864 pickedImageThumbnail.setVisibility(View.VISIBLE); 865 } else { 866 Log.e(TAG, "Error loading thumbnail for uri=" + uri); 867 } 868 } 869 }.execute(); 870 871 UriWallpaperInfo info = new UriWallpaperInfo(uri); 872 pickedImageThumbnail.setTag(info); 873 info.setView(pickedImageThumbnail); 874 addLongPressHandler(pickedImageThumbnail); 875 updateTileIndices(); 876 pickedImageThumbnail.setOnClickListener(mThumbnailOnClickListener); 877 if (!fromRestore) { 878 mThumbnailOnClickListener.onClick(pickedImageThumbnail); 879 } 880 } 881 onActivityResult(int requestCode, int resultCode, Intent data)882 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 883 if (requestCode == IMAGE_PICK && resultCode == RESULT_OK) { 884 if (data != null && data.getData() != null) { 885 Uri uri = data.getData(); 886 addTemporaryWallpaperTile(uri, false); 887 } 888 } else if (requestCode == PICK_WALLPAPER_THIRD_PARTY_ACTIVITY) { 889 setResult(RESULT_OK); 890 finish(); 891 } else if (requestCode == PICK_LIVE_WALLPAPER) { 892 WallpaperManager wm = WallpaperManager.getInstance(this); 893 final WallpaperInfo oldLiveWallpaper = mLiveWallpaperInfoOnPickerLaunch; 894 final WallpaperInfo clickedWallpaper = mLastClickedLiveWallpaperInfo; 895 WallpaperInfo newLiveWallpaper = wm.getWallpaperInfo(); 896 // Try to figure out if a live wallpaper was set; 897 if (newLiveWallpaper != null && 898 (oldLiveWallpaper == null 899 || !oldLiveWallpaper.getComponent() 900 .equals(newLiveWallpaper.getComponent()) 901 || clickedWallpaper.getComponent() 902 .equals(oldLiveWallpaper.getComponent()))) { 903 // Return if a live wallpaper was set 904 setResult(RESULT_OK); 905 finish(); 906 } 907 } 908 } 909 setWallpaperItemPaddingToZero(FrameLayout frameLayout)910 static void setWallpaperItemPaddingToZero(FrameLayout frameLayout) { 911 frameLayout.setPadding(0, 0, 0, 0); 912 frameLayout.setForeground(new ZeroPaddingDrawable(frameLayout.getForeground())); 913 } 914 addLongPressHandler(View v)915 private void addLongPressHandler(View v) { 916 v.setOnLongClickListener(mLongClickListener); 917 } 918 findBundledWallpapers()919 private ArrayList<WallpaperTileInfo> findBundledWallpapers() { 920 final PackageManager pm = getPackageManager(); 921 final ArrayList<WallpaperTileInfo> bundled = new ArrayList<WallpaperTileInfo>(24); 922 923 Partner partner = Partner.get(pm); 924 if (partner != null) { 925 final Resources partnerRes = partner.getResources(); 926 final int resId = partnerRes.getIdentifier(Partner.RES_WALLPAPERS, "array", 927 partner.getPackageName()); 928 if (resId != 0) { 929 addWallpapers(bundled, partnerRes, partner.getPackageName(), resId); 930 } 931 932 // Add system wallpapers 933 File systemDir = partner.getWallpaperDirectory(); 934 if (systemDir != null && systemDir.isDirectory()) { 935 for (File file : systemDir.listFiles()) { 936 if (!file.isFile()) { 937 continue; 938 } 939 String name = file.getName(); 940 int dotPos = name.lastIndexOf('.'); 941 String extension = ""; 942 if (dotPos >= -1) { 943 extension = name.substring(dotPos); 944 name = name.substring(0, dotPos); 945 } 946 947 if (name.endsWith("_small")) { 948 // it is a thumbnail 949 continue; 950 } 951 952 File thumbnail = new File(systemDir, name + "_small" + extension); 953 Bitmap thumb = BitmapFactory.decodeFile(thumbnail.getAbsolutePath()); 954 if (thumb != null) { 955 bundled.add(new FileWallpaperInfo(file, new BitmapDrawable(thumb))); 956 } 957 } 958 } 959 } 960 961 Pair<ApplicationInfo, Integer> r = getWallpaperArrayResourceId(); 962 if (r != null) { 963 try { 964 Resources wallpaperRes = getPackageManager().getResourcesForApplication(r.first); 965 addWallpapers(bundled, wallpaperRes, r.first.packageName, r.second); 966 } catch (PackageManager.NameNotFoundException e) { 967 } 968 } 969 970 if (partner == null || !partner.hideDefaultWallpaper()) { 971 // Add an entry for the default wallpaper (stored in system resources) 972 WallpaperTileInfo defaultWallpaperInfo = 973 (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) 974 ? getPreKKDefaultWallpaperInfo() 975 : getDefaultWallpaper(); 976 if (defaultWallpaperInfo != null) { 977 bundled.add(0, defaultWallpaperInfo); 978 } 979 } 980 return bundled; 981 } 982 writeImageToFileAsJpeg(File f, Bitmap b)983 private boolean writeImageToFileAsJpeg(File f, Bitmap b) { 984 try { 985 f.createNewFile(); 986 FileOutputStream thumbFileStream = 987 openFileOutput(f.getName(), Context.MODE_PRIVATE); 988 b.compress(Bitmap.CompressFormat.JPEG, 95, thumbFileStream); 989 thumbFileStream.close(); 990 return true; 991 } catch (IOException e) { 992 Log.e(TAG, "Error while writing bitmap to file " + e); 993 f.delete(); 994 } 995 return false; 996 } 997 getDefaultThumbFile()998 private File getDefaultThumbFile() { 999 return new File(getFilesDir(), Build.VERSION.SDK_INT 1000 + "_" + LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL); 1001 } 1002 saveDefaultWallpaperThumb(Bitmap b)1003 private boolean saveDefaultWallpaperThumb(Bitmap b) { 1004 // Delete old thumbnails. 1005 new File(getFilesDir(), LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL_OLD).delete(); 1006 new File(getFilesDir(), LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL).delete(); 1007 1008 for (int i = Build.VERSION_CODES.JELLY_BEAN; i < Build.VERSION.SDK_INT; i++) { 1009 new File(getFilesDir(), i + "_" + LauncherFiles.DEFAULT_WALLPAPER_THUMBNAIL).delete(); 1010 } 1011 return writeImageToFileAsJpeg(getDefaultThumbFile(), b); 1012 } 1013 getPreKKDefaultWallpaperInfo()1014 private ResourceWallpaperInfo getPreKKDefaultWallpaperInfo() { 1015 Resources sysRes = Resources.getSystem(); 1016 int resId = sysRes.getIdentifier("default_wallpaper", "drawable", "android"); 1017 1018 File defaultThumbFile = getDefaultThumbFile(); 1019 Bitmap thumb = null; 1020 boolean defaultWallpaperExists = false; 1021 if (defaultThumbFile.exists()) { 1022 thumb = BitmapFactory.decodeFile(defaultThumbFile.getAbsolutePath()); 1023 defaultWallpaperExists = true; 1024 } else { 1025 Resources res = getResources(); 1026 Point defaultThumbSize = getDefaultThumbnailSize(res); 1027 int rotation = WallpaperCropActivity.getRotationFromExif(res, resId); 1028 thumb = createThumbnail( 1029 defaultThumbSize, this, null, null, sysRes, resId, rotation, false); 1030 if (thumb != null) { 1031 defaultWallpaperExists = saveDefaultWallpaperThumb(thumb); 1032 } 1033 } 1034 if (defaultWallpaperExists) { 1035 return new ResourceWallpaperInfo(sysRes, resId, new BitmapDrawable(thumb)); 1036 } 1037 return null; 1038 } 1039 1040 @TargetApi(Build.VERSION_CODES.KITKAT) getDefaultWallpaper()1041 private DefaultWallpaperInfo getDefaultWallpaper() { 1042 File defaultThumbFile = getDefaultThumbFile(); 1043 Bitmap thumb = null; 1044 boolean defaultWallpaperExists = false; 1045 if (defaultThumbFile.exists()) { 1046 thumb = BitmapFactory.decodeFile(defaultThumbFile.getAbsolutePath()); 1047 defaultWallpaperExists = true; 1048 } else { 1049 Resources res = getResources(); 1050 Point defaultThumbSize = getDefaultThumbnailSize(res); 1051 Drawable wallpaperDrawable = WallpaperManager.getInstance(this).getBuiltInDrawable( 1052 defaultThumbSize.x, defaultThumbSize.y, true, 0.5f, 0.5f); 1053 if (wallpaperDrawable != null) { 1054 thumb = Bitmap.createBitmap( 1055 defaultThumbSize.x, defaultThumbSize.y, Bitmap.Config.ARGB_8888); 1056 Canvas c = new Canvas(thumb); 1057 wallpaperDrawable.setBounds(0, 0, defaultThumbSize.x, defaultThumbSize.y); 1058 wallpaperDrawable.draw(c); 1059 c.setBitmap(null); 1060 } 1061 if (thumb != null) { 1062 defaultWallpaperExists = saveDefaultWallpaperThumb(thumb); 1063 } 1064 } 1065 if (defaultWallpaperExists) { 1066 return new DefaultWallpaperInfo(new BitmapDrawable(thumb)); 1067 } 1068 return null; 1069 } 1070 getWallpaperArrayResourceId()1071 public Pair<ApplicationInfo, Integer> getWallpaperArrayResourceId() { 1072 // Context.getPackageName() may return the "original" package name, 1073 // com.android.launcher3; Resources needs the real package name, 1074 // com.android.launcher3. So we ask Resources for what it thinks the 1075 // package name should be. 1076 final String packageName = getResources().getResourcePackageName(R.array.wallpapers); 1077 try { 1078 ApplicationInfo info = getPackageManager().getApplicationInfo(packageName, 0); 1079 return new Pair<ApplicationInfo, Integer>(info, R.array.wallpapers); 1080 } catch (PackageManager.NameNotFoundException e) { 1081 return null; 1082 } 1083 } 1084 addWallpapers(ArrayList<WallpaperTileInfo> known, Resources res, String packageName, int listResId)1085 private void addWallpapers(ArrayList<WallpaperTileInfo> known, Resources res, 1086 String packageName, int listResId) { 1087 final String[] extras = res.getStringArray(listResId); 1088 for (String extra : extras) { 1089 int resId = res.getIdentifier(extra, "drawable", packageName); 1090 if (resId != 0) { 1091 final int thumbRes = res.getIdentifier(extra + "_small", "drawable", packageName); 1092 1093 if (thumbRes != 0) { 1094 ResourceWallpaperInfo wallpaperInfo = 1095 new ResourceWallpaperInfo(res, resId, res.getDrawable(thumbRes)); 1096 known.add(wallpaperInfo); 1097 // Log.d(TAG, "add: [" + packageName + "]: " + extra + " (" + res + ")"); 1098 } 1099 } else { 1100 Log.e(TAG, "Couldn't find wallpaper " + extra); 1101 } 1102 } 1103 } 1104 getCropView()1105 public CropView getCropView() { 1106 return mCropView; 1107 } 1108 getSavedImages()1109 public SavedWallpaperImages getSavedImages() { 1110 return mSavedImages; 1111 } 1112 onLiveWallpaperPickerLaunch(WallpaperInfo info)1113 public void onLiveWallpaperPickerLaunch(WallpaperInfo info) { 1114 mLastClickedLiveWallpaperInfo = info; 1115 mLiveWallpaperInfoOnPickerLaunch = WallpaperManager.getInstance(this).getWallpaperInfo(); 1116 } 1117 1118 static class ZeroPaddingDrawable extends LevelListDrawable { ZeroPaddingDrawable(Drawable d)1119 public ZeroPaddingDrawable(Drawable d) { 1120 super(); 1121 addLevel(0, 0, d); 1122 setLevel(0); 1123 } 1124 1125 @Override getPadding(Rect padding)1126 public boolean getPadding(Rect padding) { 1127 padding.set(0, 0, 0, 0); 1128 return true; 1129 } 1130 } 1131 1132 private static class SimpleWallpapersAdapter extends ArrayAdapter<WallpaperTileInfo> { 1133 private final LayoutInflater mLayoutInflater; 1134 SimpleWallpapersAdapter(Activity activity, ArrayList<WallpaperTileInfo> wallpapers)1135 SimpleWallpapersAdapter(Activity activity, ArrayList<WallpaperTileInfo> wallpapers) { 1136 super(activity, R.layout.wallpaper_picker_item, wallpapers); 1137 mLayoutInflater = activity.getLayoutInflater(); 1138 } 1139 getView(int position, View convertView, ViewGroup parent)1140 public View getView(int position, View convertView, ViewGroup parent) { 1141 Drawable thumb = getItem(position).mThumb; 1142 if (thumb == null) { 1143 Log.e(TAG, "Error decoding thumbnail for wallpaper #" + position); 1144 } 1145 return createImageTileView(mLayoutInflater, convertView, parent, thumb); 1146 } 1147 } 1148 createImageTileView(LayoutInflater layoutInflater, View convertView, ViewGroup parent, Drawable thumb)1149 public static View createImageTileView(LayoutInflater layoutInflater, 1150 View convertView, ViewGroup parent, Drawable thumb) { 1151 View view; 1152 1153 if (convertView == null) { 1154 view = layoutInflater.inflate(R.layout.wallpaper_picker_item, parent, false); 1155 } else { 1156 view = convertView; 1157 } 1158 1159 setWallpaperItemPaddingToZero((FrameLayout) view); 1160 1161 ImageView image = (ImageView) view.findViewById(R.id.wallpaper_image); 1162 1163 if (thumb != null) { 1164 image.setImageDrawable(thumb); 1165 thumb.setDither(true); 1166 } 1167 1168 return view; 1169 } 1170 1171 // In Launcher3, we override this with a method that catches exceptions 1172 // from starting activities; didn't want to copy and paste code into here startActivityForResultSafely(Intent intent, int requestCode)1173 public void startActivityForResultSafely(Intent intent, int requestCode) { 1174 startActivityForResult(intent, requestCode); 1175 } 1176 } 1177