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.getWallpaperId(WallpaperManager.FLAG_LOCK) < 0) { 257 // The lock screen does not have a wallpaper, so no need to prompt; can only set both. 258 try { 259 setLiveWallpaper(v.getRootView().getWindowToken()); 260 setResult(RESULT_OK); 261 } catch (RuntimeException e) { 262 Log.w(LOG_TAG, "Failure setting wallpaper", e); 263 } 264 finish(); 265 } else { 266 // Otherwise, prompt to either set on home or both home and lock screen. 267 Context themedContext = new ContextThemeWrapper(this, android.R.style.Theme_DeviceDefault_Settings); 268 new AlertDialog.Builder(themedContext) 269 .setTitle(R.string.set_live_wallpaper) 270 .setAdapter(new WallpaperTargetAdapter(themedContext), new DialogInterface.OnClickListener() { 271 @Override 272 public void onClick(DialogInterface dialog, int which) { 273 try { 274 setLiveWallpaper(v.getRootView().getWindowToken()); 275 if (which == 1) { 276 // "Home screen and lock screen"; clear the lock screen so it 277 // shows through to the live wallpaper on home. 278 mWallpaperManager.clear(WallpaperManager.FLAG_LOCK); 279 } 280 setResult(RESULT_OK); 281 } catch (RuntimeException|IOException e) { 282 Log.w(LOG_TAG, "Failure setting wallpaper", e); 283 } 284 finish(); 285 } 286 }) 287 .show(); 288 } 289 } 290 291 private void setLiveWallpaper(IBinder windowToken) { 292 mWallpaperManager.setWallpaperComponent(mWallpaperIntent.getComponent()); 293 mWallpaperManager.setWallpaperOffsetSteps(0.5f, 0.0f); 294 mWallpaperManager.setWallpaperOffsets(windowToken, 0.5f, 0.0f); 295 } 296 297 @Override 298 public boolean onOptionsItemSelected(MenuItem item) { 299 int id = item.getItemId(); 300 if (id == R.id.configure) { 301 Intent intent = new Intent(); 302 intent.setComponent(new ComponentName(mPackageName, mSettings)); 303 intent.putExtra(WallpaperSettingsActivity.EXTRA_PREVIEW_MODE, true); 304 startActivity(intent); 305 return true; 306 } else if (id == R.id.set_wallpaper) { 307 setLiveWallpaper(getWindow().getDecorView()); 308 return true; 309 } else if (id == android.R.id.home) { 310 onBackPressed(); 311 return true; 312 } 313 return super.onOptionsItemSelected(item); 314 } 315 316 @Override 317 public void onResume() { 318 super.onResume(); 319 if (mWallpaperConnection != null && mWallpaperConnection.mEngine != null) { 320 try { 321 mWallpaperConnection.mEngine.setVisibility(true); 322 } catch (RemoteException e) { 323 // Ignore 324 } 325 } 326 } 327 328 @Override 329 public void onPause() { 330 super.onPause(); 331 if (mWallpaperConnection != null && mWallpaperConnection.mEngine != null) { 332 try { 333 mWallpaperConnection.mEngine.setVisibility(false); 334 } catch (RemoteException e) { 335 // Ignore 336 } 337 } 338 } 339 340 @Override 341 public void onAttachedToWindow() { 342 super.onAttachedToWindow(); 343 344 getWindow().getDecorView().post(new Runnable() { 345 public void run() { 346 if (!mWallpaperConnection.connect()) { 347 mWallpaperConnection = null; 348 } 349 } 350 }); 351 } 352 353 @Override 354 public void onDetachedFromWindow() { 355 super.onDetachedFromWindow(); 356 357 if (mWallpaperConnection != null) { 358 mWallpaperConnection.disconnect(); 359 } 360 mWallpaperConnection = null; 361 } 362 363 @Override 364 public boolean dispatchTouchEvent(MotionEvent ev) { 365 if (mWallpaperConnection != null && mWallpaperConnection.mEngine != null) { 366 MotionEvent dup = MotionEvent.obtainNoHistory(ev); 367 try { 368 mWallpaperConnection.mEngine.dispatchPointer(dup); 369 } catch (RemoteException e) { 370 } 371 } 372 373 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 374 onUserInteraction(); 375 } 376 boolean handled = getWindow().superDispatchTouchEvent(ev); 377 if (!handled) { 378 handled = onTouchEvent(ev); 379 } 380 381 if (!handled && mWallpaperConnection != null && mWallpaperConnection.mEngine != null) { 382 int action = ev.getActionMasked(); 383 try { 384 if (action == MotionEvent.ACTION_UP) { 385 mWallpaperConnection.mEngine.dispatchWallpaperCommand( 386 WallpaperManager.COMMAND_TAP, 387 (int) ev.getX(), (int) ev.getY(), 0, null); 388 } else if (action == MotionEvent.ACTION_POINTER_UP) { 389 int pointerIndex = ev.getActionIndex(); 390 mWallpaperConnection.mEngine.dispatchWallpaperCommand( 391 WallpaperManager.COMMAND_SECONDARY_TAP, 392 (int) ev.getX(pointerIndex), (int) ev.getY(pointerIndex), 0, null); 393 } 394 } catch (RemoteException e) { 395 } 396 } 397 return handled; 398 } 399 400 class WallpaperConnection extends IWallpaperConnection.Stub implements ServiceConnection { 401 final Intent mIntent; 402 IWallpaperService mService; 403 IWallpaperEngine mEngine; 404 boolean mConnected; 405 406 WallpaperConnection(Intent intent) { 407 mIntent = intent; 408 } 409 410 public boolean connect() { 411 synchronized (this) { 412 if (!bindService(mIntent, this, Context.BIND_AUTO_CREATE)) { 413 return false; 414 } 415 416 mConnected = true; 417 return true; 418 } 419 } 420 421 public void disconnect() { 422 synchronized (this) { 423 mConnected = false; 424 if (mEngine != null) { 425 try { 426 mEngine.destroy(); 427 } catch (RemoteException e) { 428 // Ignore 429 } 430 mEngine = null; 431 } 432 unbindService(this); 433 mService = null; 434 } 435 } 436 437 public void onServiceConnected(ComponentName name, IBinder service) { 438 if (mWallpaperConnection == this) { 439 mService = IWallpaperService.Stub.asInterface(service); 440 try { 441 final View root = getWindow().getDecorView(); 442 mService.attach(this, root.getWindowToken(), 443 LayoutParams.TYPE_APPLICATION_MEDIA, 444 true, root.getWidth(), root.getHeight(), 445 new Rect(0, 0, 0, 0)); 446 } catch (RemoteException e) { 447 Log.w(LOG_TAG, "Failed attaching wallpaper; clearing", e); 448 } 449 } 450 } 451 452 public void onServiceDisconnected(ComponentName name) { 453 mService = null; 454 mEngine = null; 455 if (mWallpaperConnection == this) { 456 Log.w(LOG_TAG, "Wallpaper service gone: " + name); 457 } 458 } 459 460 public void attachEngine(IWallpaperEngine engine) { 461 synchronized (this) { 462 if (mConnected) { 463 mEngine = engine; 464 try { 465 engine.setVisibility(true); 466 } catch (RemoteException e) { 467 // Ignore 468 } 469 } else { 470 try { 471 engine.destroy(); 472 } catch (RemoteException e) { 473 // Ignore 474 } 475 } 476 } 477 } 478 479 public ParcelFileDescriptor setWallpaper(String name) { 480 return null; 481 } 482 483 @Override 484 public void engineShown(IWallpaperEngine engine) throws RemoteException { 485 mLoading.post(() -> { 486 mLoading.animate() 487 .alpha(0f) 488 .setDuration(220) 489 .setInterpolator(AnimationUtils.loadInterpolator(LiveWallpaperPreview.this, 490 android.R.interpolator.fast_out_linear_in)) 491 .withEndAction(() -> mLoading.setVisibility(View.INVISIBLE)); 492 }); 493 } 494 } 495 496 private static class WallpaperTargetAdapter extends ArrayAdapter<CharSequence> { 497 498 public WallpaperTargetAdapter(Context context) { 499 super(context, R.layout.wallpaper_target_dialog_item, 500 context.getResources().getTextArray(R.array.which_wallpaper_options)); 501 } 502 503 @Override 504 public boolean hasStableIds() { 505 return true; 506 } 507 508 @Override 509 public long getItemId(int position) { 510 return position; 511 } 512 513 @Override 514 public View getView(int position, View convertView, ViewGroup parent) { 515 TextView tv = (TextView) super.getView(position, convertView, parent); 516 tv.setCompoundDrawablesRelativeWithIntrinsicBounds( 517 position == 0 ? R.drawable.ic_home : R.drawable.ic_device, 0, 0, 0); 518 return tv; 519 } 520 } 521 } 522