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