1 /* 2 * Copyright (C) 2009 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.wallpaper.livepicker; 18 19 import android.app.Activity; 20 import android.app.AlertDialog; 21 import android.app.WallpaperColors; 22 import android.app.WallpaperInfo; 23 import android.app.WallpaperManager; 24 import android.content.ActivityNotFoundException; 25 import android.content.ComponentName; 26 import android.content.Context; 27 import android.content.DialogInterface; 28 import android.content.Intent; 29 import android.content.ServiceConnection; 30 import android.content.pm.ActivityInfo; 31 import android.content.pm.PackageManager; 32 import android.content.res.Resources.NotFoundException; 33 import android.graphics.Rect; 34 import android.graphics.drawable.Drawable; 35 import android.net.Uri; 36 import android.os.Bundle; 37 import android.os.IBinder; 38 import android.os.ParcelFileDescriptor; 39 import android.os.RemoteException; 40 import android.service.wallpaper.IWallpaperConnection; 41 import android.service.wallpaper.IWallpaperEngine; 42 import android.service.wallpaper.IWallpaperService; 43 import android.service.wallpaper.WallpaperService; 44 import android.service.wallpaper.WallpaperSettingsActivity; 45 import android.support.design.widget.BottomSheetBehavior; 46 import android.text.TextUtils; 47 import android.util.Log; 48 import android.view.ContextThemeWrapper; 49 import android.view.Menu; 50 import android.view.MenuItem; 51 import android.view.MotionEvent; 52 import android.view.View; 53 import android.view.View.OnClickListener; 54 import android.view.ViewGroup; 55 import android.view.WindowManager.LayoutParams; 56 import android.view.animation.AnimationUtils; 57 import android.widget.ArrayAdapter; 58 import android.widget.Button; 59 import android.widget.ImageButton; 60 import android.widget.TextView; 61 import android.widget.Toolbar; 62 63 import java.io.IOException; 64 65 public class LiveWallpaperPreview extends Activity { 66 static final String EXTRA_LIVE_WALLPAPER_INFO = "android.live_wallpaper.info"; 67 68 private static final String LOG_TAG = "LiveWallpaperPreview"; 69 70 private static final boolean SHOW_DUMMY_DATA = false; 71 72 private WallpaperManager mWallpaperManager; 73 private WallpaperConnection mWallpaperConnection; 74 75 private String mPackageName; 76 private Intent mWallpaperIntent; 77 private Intent mSettingsIntent; 78 79 private TextView mAttributionTitle; 80 private TextView mAttributionSubtitle1; 81 private TextView mAttributionSubtitle2; 82 private Button mAttributionExploreButton; 83 private ImageButton mPreviewPaneArrow; 84 private View mBottomSheet; 85 private View mSpacer; 86 private View mLoading; 87 88 @Override onCreate(Bundle savedInstanceState)89 protected void onCreate(Bundle savedInstanceState) { 90 super.onCreate(savedInstanceState); 91 init(); 92 } 93 init()94 protected void init() { 95 Bundle extras = getIntent().getExtras(); 96 WallpaperInfo info = extras.getParcelable(EXTRA_LIVE_WALLPAPER_INFO); 97 if (info == null) { 98 setResult(RESULT_CANCELED); 99 finish(); 100 } 101 initUI(info); 102 } 103 initUI(WallpaperInfo info)104 protected void initUI(WallpaperInfo info) { 105 getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE 106 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 107 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); 108 setContentView(R.layout.live_wallpaper_preview); 109 mAttributionTitle = (TextView) findViewById(R.id.preview_attribution_pane_title); 110 mAttributionSubtitle1 = (TextView) findViewById(R.id.preview_attribution_pane_subtitle1); 111 mAttributionSubtitle2 = (TextView) findViewById(R.id.preview_attribution_pane_subtitle2); 112 mAttributionExploreButton = (Button) findViewById( 113 R.id.preview_attribution_pane_explore_button); 114 mPreviewPaneArrow = (ImageButton) findViewById(R.id.preview_attribution_pane_arrow); 115 mBottomSheet = findViewById(R.id.bottom_sheet); 116 mSpacer = findViewById(R.id.spacer); 117 mLoading = findViewById(R.id.loading); 118 119 mPackageName = info.getPackageName(); 120 mWallpaperIntent = new Intent(WallpaperService.SERVICE_INTERFACE) 121 .setClassName(info.getPackageName(), info.getServiceName()); 122 123 final String settingsActivity = info.getSettingsActivity(); 124 if (settingsActivity != null) { 125 mSettingsIntent = new Intent(); 126 mSettingsIntent.setComponent(new ComponentName(mPackageName, settingsActivity)); 127 mSettingsIntent.putExtra(WallpaperSettingsActivity.EXTRA_PREVIEW_MODE, true); 128 final PackageManager pm = getPackageManager(); 129 final ActivityInfo activityInfo = mSettingsIntent.resolveActivityInfo(pm, 0); 130 if (activityInfo == null) { 131 Log.e(LOG_TAG, "Couldn't find settings activity: " + settingsActivity); 132 mSettingsIntent = null; 133 } 134 } 135 136 Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 137 setActionBar(toolbar); 138 getActionBar().setDisplayHomeAsUpEnabled(true); 139 getActionBar().setDisplayShowTitleEnabled(false); 140 141 Drawable backArrow = getResources().getDrawable(R.drawable.ic_arrow_back_white_24dp); 142 backArrow.setAutoMirrored(true); 143 toolbar.setNavigationIcon(backArrow); 144 145 mWallpaperManager = WallpaperManager.getInstance(this); 146 mWallpaperConnection = new WallpaperConnection(mWallpaperIntent); 147 148 populateAttributionPane(info); 149 } 150 populateAttributionPane(WallpaperInfo info)151 private void populateAttributionPane(WallpaperInfo info) { 152 if (!info.getShowMetadataInPreview() && !SHOW_DUMMY_DATA) { 153 mBottomSheet.setVisibility(View.GONE); 154 return; 155 } 156 final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(mBottomSheet); 157 158 OnClickListener onClickListener = new OnClickListener() { 159 @Override 160 public void onClick(View view) { 161 if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) { 162 bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); 163 } else if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) { 164 bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); 165 } 166 } 167 }; 168 mAttributionTitle.setOnClickListener(onClickListener); 169 mPreviewPaneArrow.setOnClickListener(onClickListener); 170 171 bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { 172 @Override 173 public void onStateChanged(View bottomSheet, int newState) { 174 if (newState == BottomSheetBehavior.STATE_COLLAPSED) { 175 mPreviewPaneArrow.setImageResource(R.drawable.ic_keyboard_arrow_up_white_24dp); 176 mPreviewPaneArrow.setContentDescription( 177 getResources().getString(R.string.expand_attribution_panel)); 178 } else if (newState == BottomSheetBehavior.STATE_EXPANDED) { 179 mPreviewPaneArrow.setImageResource( 180 R.drawable.ic_keyboard_arrow_down_white_24dp); 181 mPreviewPaneArrow.setContentDescription( 182 getResources().getString(R.string.collapse_attribution_panel)); 183 } 184 } 185 186 @Override 187 public void onSlide(View bottomSheet, float slideOffset) { 188 float alpha; 189 if (slideOffset >= 0) { 190 alpha = slideOffset; 191 } else { 192 alpha = 1f - slideOffset; 193 } 194 mAttributionTitle.setAlpha(slideOffset); 195 mAttributionSubtitle1.setAlpha(slideOffset); 196 mAttributionSubtitle2.setAlpha(slideOffset); 197 mAttributionExploreButton.setAlpha(slideOffset); 198 } 199 }); 200 201 bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); 202 mPreviewPaneArrow.setImageResource(R.drawable.ic_keyboard_arrow_down_white_24dp); 203 204 if (SHOW_DUMMY_DATA) { 205 mAttributionTitle.setText("Diorama, Yosemite"); 206 mAttributionSubtitle1.setText("Live Earth Collection - Android Earth"); 207 mAttributionSubtitle2.setText("Lorem ipsum dolor sit amet, consectetur adipiscing elit." 208 + " Sed imperdiet et mauris molestie laoreet. Proin volutpat elit nec magna" 209 + " tempus, ac aliquet lectus volutpat."); 210 mAttributionExploreButton.setText("Explore"); 211 } else { 212 PackageManager pm = getPackageManager(); 213 214 CharSequence title = info.loadLabel(pm); 215 if (!TextUtils.isEmpty(title)) { 216 mAttributionTitle.setText(title); 217 } else { 218 mAttributionTitle.setVisibility(View.GONE); 219 } 220 221 try { 222 CharSequence author = info.loadAuthor(pm); 223 if (TextUtils.isEmpty(author)) { 224 throw new NotFoundException(); 225 } 226 mAttributionSubtitle1.setText(author); 227 } catch (NotFoundException e) { 228 mAttributionSubtitle1.setVisibility(View.GONE); 229 } 230 231 try { 232 CharSequence description = info.loadDescription(pm); 233 if (TextUtils.isEmpty(description)) { 234 throw new NotFoundException(); 235 } 236 mAttributionSubtitle2.setText(description); 237 } catch (NotFoundException e) { 238 mAttributionSubtitle2.setVisibility(View.GONE); 239 } 240 241 try { 242 Uri contextUri = info.loadContextUri(pm); 243 CharSequence contextDescription = info.loadContextDescription(pm); 244 if (contextUri == null) { 245 throw new NotFoundException(); 246 } 247 mAttributionExploreButton.setText(contextDescription); 248 mAttributionExploreButton.setOnClickListener(v -> { 249 Intent intent = new Intent(Intent.ACTION_VIEW, contextUri); 250 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 251 try { 252 startActivity(intent); 253 } catch (ActivityNotFoundException e) { 254 Log.e(LOG_TAG, "Couldn't find activity for context link.", e); 255 } 256 }); 257 } catch (NotFoundException e) { 258 mAttributionExploreButton.setVisibility(View.GONE); 259 mSpacer.setVisibility(View.VISIBLE); 260 } 261 } 262 263 264 } 265 266 @Override onCreateOptionsMenu(Menu menu)267 public boolean onCreateOptionsMenu(Menu menu) { 268 getMenuInflater().inflate(R.menu.menu_preview, menu); 269 menu.findItem(R.id.configure).setVisible(mSettingsIntent != null); 270 menu.findItem(R.id.set_wallpaper).getActionView().setOnClickListener( 271 this::setLiveWallpaper); 272 return super.onCreateOptionsMenu(menu); 273 } 274 setLiveWallpaper(final View v)275 public void setLiveWallpaper(final View v) { 276 if (mWallpaperManager.getWallpaperInfo() != null 277 && mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_LOCK) < 0) { 278 // The lock screen does not have a distinct wallpaper and the current wallpaper is a 279 // live wallpaper, so since we cannot preserve any static imagery on the lock screen, 280 // set the live wallpaper directly without giving the user a destination option. 281 try { 282 setLiveWallpaper(v.getRootView().getWindowToken()); 283 setResult(RESULT_OK); 284 } catch (RuntimeException e) { 285 Log.w(LOG_TAG, "Failure setting wallpaper", e); 286 } 287 finish(); 288 } else { 289 // Otherwise, prompt to either set on home or both home and lock screen. 290 Context themedContext = new ContextThemeWrapper(this, android.R.style.Theme_DeviceDefault_Settings); 291 new AlertDialog.Builder(themedContext) 292 .setTitle(R.string.set_live_wallpaper) 293 .setAdapter(new WallpaperTargetAdapter(themedContext), new DialogInterface.OnClickListener() { 294 @Override 295 public void onClick(DialogInterface dialog, int which) { 296 try { 297 setLiveWallpaper(v.getRootView().getWindowToken()); 298 if (which == 1) { 299 // "Home screen and lock screen"; clear the lock screen so it 300 // shows through to the live wallpaper on home. 301 mWallpaperManager.clear(WallpaperManager.FLAG_LOCK); 302 } 303 setResult(RESULT_OK); 304 } catch (RuntimeException|IOException e) { 305 Log.w(LOG_TAG, "Failure setting wallpaper", e); 306 } 307 finish(); 308 } 309 }) 310 .show(); 311 } 312 } 313 setLiveWallpaper(IBinder windowToken)314 private void setLiveWallpaper(IBinder windowToken) { 315 mWallpaperManager.setWallpaperComponent(mWallpaperIntent.getComponent()); 316 mWallpaperManager.setWallpaperOffsetSteps(0.5f, 0.0f); 317 mWallpaperManager.setWallpaperOffsets(windowToken, 0.5f, 0.0f); 318 } 319 320 @Override onOptionsItemSelected(MenuItem item)321 public boolean onOptionsItemSelected(MenuItem item) { 322 int id = item.getItemId(); 323 if (id == R.id.configure) { 324 startActivity(mSettingsIntent); 325 return true; 326 } else if (id == R.id.set_wallpaper) { 327 setLiveWallpaper(getWindow().getDecorView()); 328 return true; 329 } else if (id == android.R.id.home) { 330 onBackPressed(); 331 return true; 332 } 333 return super.onOptionsItemSelected(item); 334 } 335 336 @Override onResume()337 public void onResume() { 338 super.onResume(); 339 if (mWallpaperConnection != null && mWallpaperConnection.mEngine != null) { 340 try { 341 mWallpaperConnection.mEngine.setVisibility(true); 342 } catch (RemoteException e) { 343 // Ignore 344 } 345 } 346 } 347 348 @Override onPause()349 public void onPause() { 350 super.onPause(); 351 if (mWallpaperConnection != null && mWallpaperConnection.mEngine != null) { 352 try { 353 mWallpaperConnection.mEngine.setVisibility(false); 354 } catch (RemoteException e) { 355 // Ignore 356 } 357 } 358 } 359 360 @Override onAttachedToWindow()361 public void onAttachedToWindow() { 362 super.onAttachedToWindow(); 363 364 getWindow().getDecorView().post(new Runnable() { 365 public void run() { 366 if (!mWallpaperConnection.connect()) { 367 mWallpaperConnection = null; 368 } 369 } 370 }); 371 } 372 373 @Override onDetachedFromWindow()374 public void onDetachedFromWindow() { 375 super.onDetachedFromWindow(); 376 377 if (mWallpaperConnection != null) { 378 mWallpaperConnection.disconnect(); 379 } 380 mWallpaperConnection = null; 381 } 382 383 @Override dispatchTouchEvent(MotionEvent ev)384 public boolean dispatchTouchEvent(MotionEvent ev) { 385 if (mWallpaperConnection != null && mWallpaperConnection.mEngine != null) { 386 MotionEvent dup = MotionEvent.obtainNoHistory(ev); 387 try { 388 mWallpaperConnection.mEngine.dispatchPointer(dup); 389 } catch (RemoteException e) { 390 } 391 } 392 393 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 394 onUserInteraction(); 395 } 396 boolean handled = getWindow().superDispatchTouchEvent(ev); 397 if (!handled) { 398 handled = onTouchEvent(ev); 399 } 400 401 if (!handled && mWallpaperConnection != null && mWallpaperConnection.mEngine != null) { 402 int action = ev.getActionMasked(); 403 try { 404 if (action == MotionEvent.ACTION_UP) { 405 mWallpaperConnection.mEngine.dispatchWallpaperCommand( 406 WallpaperManager.COMMAND_TAP, 407 (int) ev.getX(), (int) ev.getY(), 0, null); 408 } else if (action == MotionEvent.ACTION_POINTER_UP) { 409 int pointerIndex = ev.getActionIndex(); 410 mWallpaperConnection.mEngine.dispatchWallpaperCommand( 411 WallpaperManager.COMMAND_SECONDARY_TAP, 412 (int) ev.getX(pointerIndex), (int) ev.getY(pointerIndex), 0, null); 413 } 414 } catch (RemoteException e) { 415 } 416 } 417 return handled; 418 } 419 420 class WallpaperConnection extends IWallpaperConnection.Stub implements ServiceConnection { 421 final Intent mIntent; 422 IWallpaperService mService; 423 IWallpaperEngine mEngine; 424 boolean mConnected; 425 WallpaperConnection(Intent intent)426 WallpaperConnection(Intent intent) { 427 mIntent = intent; 428 } 429 connect()430 public boolean connect() { 431 synchronized (this) { 432 if (!bindService(mIntent, this, Context.BIND_AUTO_CREATE)) { 433 return false; 434 } 435 436 mConnected = true; 437 return true; 438 } 439 } 440 disconnect()441 public void disconnect() { 442 synchronized (this) { 443 mConnected = false; 444 if (mEngine != null) { 445 try { 446 mEngine.destroy(); 447 } catch (RemoteException e) { 448 // Ignore 449 } 450 mEngine = null; 451 } 452 try { 453 unbindService(this); 454 } catch (IllegalArgumentException e) { 455 Log.w(LOG_TAG, "Can't unbind wallpaper service. " 456 + "It might have crashed, just ignoring.", e); 457 } 458 mService = null; 459 } 460 } 461 onServiceConnected(ComponentName name, IBinder service)462 public void onServiceConnected(ComponentName name, IBinder service) { 463 if (mWallpaperConnection == this) { 464 mService = IWallpaperService.Stub.asInterface(service); 465 try { 466 final View root = getWindow().getDecorView(); 467 mService.attach(this, root.getWindowToken(), 468 LayoutParams.TYPE_APPLICATION_MEDIA, 469 true, root.getWidth(), root.getHeight(), 470 new Rect(0, 0, 0, 0)); 471 } catch (RemoteException e) { 472 Log.w(LOG_TAG, "Failed attaching wallpaper; clearing", e); 473 } 474 } 475 } 476 onServiceDisconnected(ComponentName name)477 public void onServiceDisconnected(ComponentName name) { 478 mService = null; 479 mEngine = null; 480 if (mWallpaperConnection == this) { 481 Log.w(LOG_TAG, "Wallpaper service gone: " + name); 482 } 483 } 484 attachEngine(IWallpaperEngine engine)485 public void attachEngine(IWallpaperEngine engine) { 486 synchronized (this) { 487 if (mConnected) { 488 mEngine = engine; 489 try { 490 engine.setVisibility(true); 491 } catch (RemoteException e) { 492 // Ignore 493 } 494 } else { 495 try { 496 engine.destroy(); 497 } catch (RemoteException e) { 498 // Ignore 499 } 500 } 501 } 502 } 503 setWallpaper(String name)504 public ParcelFileDescriptor setWallpaper(String name) { 505 return null; 506 } 507 508 @Override onWallpaperColorsChanged(WallpaperColors colors)509 public void onWallpaperColorsChanged(WallpaperColors colors) throws RemoteException { 510 511 } 512 513 @Override engineShown(IWallpaperEngine engine)514 public void engineShown(IWallpaperEngine engine) throws RemoteException { 515 mLoading.post(() -> { 516 mLoading.animate() 517 .alpha(0f) 518 .setDuration(220) 519 .setInterpolator(AnimationUtils.loadInterpolator(LiveWallpaperPreview.this, 520 android.R.interpolator.fast_out_linear_in)) 521 .withEndAction(() -> mLoading.setVisibility(View.INVISIBLE)); 522 }); 523 } 524 } 525 526 private static class WallpaperTargetAdapter extends ArrayAdapter<CharSequence> { 527 WallpaperTargetAdapter(Context context)528 public WallpaperTargetAdapter(Context context) { 529 super(context, R.layout.wallpaper_target_dialog_item, 530 context.getResources().getTextArray(R.array.which_wallpaper_options)); 531 } 532 533 @Override hasStableIds()534 public boolean hasStableIds() { 535 return true; 536 } 537 538 @Override getItemId(int position)539 public long getItemId(int position) { 540 return position; 541 } 542 543 @Override getView(int position, View convertView, ViewGroup parent)544 public View getView(int position, View convertView, ViewGroup parent) { 545 TextView tv = (TextView) super.getView(position, convertView, parent); 546 tv.setCompoundDrawablesRelativeWithIntrinsicBounds( 547 position == 0 ? R.drawable.ic_home : R.drawable.ic_device, 0, 0, 0); 548 return tv; 549 } 550 } 551 } 552