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.Outline;
34 import android.graphics.Rect;
35 import android.graphics.RectF;
36 import android.graphics.drawable.Drawable;
37 import android.net.Uri;
38 import android.os.Bundle;
39 import android.os.IBinder;
40 import android.os.ParcelFileDescriptor;
41 import android.os.RemoteException;
42 import android.service.wallpaper.IWallpaperConnection;
43 import android.service.wallpaper.IWallpaperEngine;
44 import android.service.wallpaper.IWallpaperService;
45 import android.service.wallpaper.WallpaperService;
46 import android.service.wallpaper.WallpaperSettingsActivity;
47 import android.text.TextUtils;
48 import android.util.Log;
49 import android.util.Pair;
50 import android.view.ContextThemeWrapper;
51 import android.view.Menu;
52 import android.view.MenuItem;
53 import android.view.MotionEvent;
54 import android.view.View;
55 import android.view.ViewGroup;
56 import android.view.ViewOutlineProvider;
57 import android.view.WindowManager.LayoutParams;
58 import android.view.animation.AnimationUtils;
59 import android.widget.ArrayAdapter;
60 import android.widget.Button;
61 import android.widget.CheckBox;
62 import android.widget.TextView;
63 import android.widget.Toolbar;
64 
65 import androidx.annotation.NonNull;
66 import androidx.annotation.Nullable;
67 import androidx.annotation.VisibleForTesting;
68 import androidx.lifecycle.LiveData;
69 import androidx.slice.Slice;
70 import androidx.slice.widget.SliceLiveData;
71 import androidx.slice.widget.SliceView;
72 import androidx.viewpager.widget.PagerAdapter;
73 import androidx.viewpager.widget.ViewPager;
74 
75 import com.google.android.material.bottomsheet.BottomSheetBehavior;
76 import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback;
77 import com.google.android.material.tabs.TabLayout;
78 
79 import java.io.IOException;
80 import java.util.ArrayList;
81 import java.util.List;
82 
83 public class LiveWallpaperPreview extends Activity {
84     static final String EXTRA_LIVE_WALLPAPER_INFO = "android.live_wallpaper.info";
85 
86     private static final String LOG_TAG = "LiveWallpaperPreview";
87 
88     private static final boolean SHOW_FAKE_DATA = false;
89 
90     private WallpaperManager mWallpaperManager;
91     private WallpaperConnection mWallpaperConnection;
92 
93     private Intent mWallpaperIntent;
94     private Intent mSettingsIntent;
95     private Intent mDeleteIntent;
96 
97     private View mLoading;
98     private View mViewBottomPane;
99     private BottomSheetBehavior mBottomSheetBehavior;
100     private ViewPager mViewPager;
101     private TabLayout mTabLayout;
102     private CheckBox mPreview;
103 
104     protected final List<Pair<String, View>> mPages = new ArrayList<>();
105     private SliceView mSliceViewSettings;
106     private LiveData<Slice> mLiveDataSettings;
107 
108     @Override
onCreate(Bundle savedInstanceState)109     protected void onCreate(Bundle savedInstanceState) {
110         super.onCreate(savedInstanceState);
111         init();
112     }
113 
init()114     protected void init() {
115         WallpaperInfo info = getIntent().getParcelableExtra(EXTRA_LIVE_WALLPAPER_INFO);
116         if (info == null) {
117             finish();
118             return;
119         }
120         initUI(info, null /* deleteAction */);
121     }
122 
initUI(WallpaperInfo info, @Nullable String deleteAction)123     protected void initUI(WallpaperInfo info, @Nullable String deleteAction) {
124         getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
125                 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
126                 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
127         setContentView(R.layout.live_wallpaper_preview);
128         mLoading = findViewById(R.id.loading);
129 
130         final String packageName = info.getPackageName();
131         mWallpaperIntent = new Intent(WallpaperService.SERVICE_INTERFACE)
132                 .setClassName(info.getPackageName(), info.getServiceName());
133 
134         final String settingsActivity = info.getSettingsActivity();
135         if (settingsActivity != null) {
136             mSettingsIntent = new Intent();
137             mSettingsIntent.setComponent(new ComponentName(packageName, settingsActivity));
138             mSettingsIntent.putExtra(WallpaperSettingsActivity.EXTRA_PREVIEW_MODE, true);
139             final PackageManager pm = getPackageManager();
140             final ActivityInfo activityInfo = mSettingsIntent.resolveActivityInfo(pm, 0);
141             if (activityInfo == null) {
142                 Log.e(LOG_TAG, "Couldn't find settings activity: " + settingsActivity);
143                 mSettingsIntent = null;
144             }
145         }
146 
147         final Toolbar toolbar = findViewById(R.id.toolbar);
148         setActionBar(toolbar);
149         getActionBar().setDisplayHomeAsUpEnabled(true);
150         getActionBar().setDisplayShowTitleEnabled(false);
151 
152         final Drawable backArrow = getDrawable(R.drawable.ic_arrow_back_white_24dp);
153         backArrow.setAutoMirrored(true);
154         toolbar.setNavigationIcon(backArrow);
155 
156         mWallpaperManager = WallpaperManager.getInstance(this);
157         mWallpaperConnection = new WallpaperConnection(mWallpaperIntent, info);
158         getWindow().getDecorView().post(new Runnable() {
159             public void run() {
160                 if (!mWallpaperConnection.connect()) {
161                     mWallpaperConnection = null;
162                 }
163             }
164         });
165 
166         if (!TextUtils.isEmpty(deleteAction)) {
167             mDeleteIntent = new Intent(deleteAction);
168             mDeleteIntent.setPackage(info.getPackageName());
169             mDeleteIntent.putExtra(EXTRA_LIVE_WALLPAPER_INFO, info);
170         }
171 
172         initInfoPage(info);
173         initSettingsPage(info);
174         populateBottomPane();
175     }
176 
initInfoPage(WallpaperInfo info)177     private void initInfoPage(WallpaperInfo info) {
178         final View pageInfo = getLayoutInflater().inflate(R.layout.page_info, null /* root */);
179         final TextView attributionTitle = pageInfo.findViewById(
180                 R.id.preview_attribution_pane_title);
181         final TextView attributionAuthor = pageInfo.findViewById(
182                 R.id.preview_attribution_pane_author);
183         final TextView attributionDescription = pageInfo.findViewById(
184                 R.id.preview_attribution_pane_description);
185         final Button attributionExploreButton = pageInfo.findViewById(
186                 R.id.preview_attribution_pane_explore_button);
187         final View spacer = pageInfo.findViewById(R.id.spacer);
188         final Button setWallpaperButton = pageInfo.findViewById(
189                 R.id.preview_attribution_pane_set_wallpaper_button);
190 
191         setWallpaperButton.setOnClickListener(this::setLiveWallpaper);
192         mPages.add(Pair.create(getString(R.string.tab_info), pageInfo));
193 
194         if (SHOW_FAKE_DATA) {
195             attributionTitle.setText("Diorama, Yosemite");
196             attributionTitle.setVisibility(View.VISIBLE);
197             attributionAuthor.setText("Live Earth Collection - Android Earth");
198             attributionAuthor.setVisibility(View.VISIBLE);
199             attributionDescription.setText("Lorem ipsum dolor sit amet, consectetur adipiscing"
200                     + " elit. Sed imperdiet et mauris molestie laoreet. Proin volutpat elit nec"
201                     + " magna tempus, ac aliquet lectus volutpat.");
202             attributionDescription.setVisibility(View.VISIBLE);
203             attributionExploreButton.setText("Explore");
204             attributionExploreButton.setVisibility(View.VISIBLE);
205             spacer.setVisibility(View.VISIBLE);
206             return;
207         }
208 
209         final PackageManager pm = getPackageManager();
210 
211         // Set attribution title
212         final CharSequence title = info.loadLabel(pm);
213         if (!TextUtils.isEmpty(title)) {
214             attributionTitle.setText(title);
215             attributionTitle.setVisibility(View.VISIBLE);
216         }
217 
218         // Don't show other meta data if attribute showMetadataInPreview is set to False
219         if (!info.getShowMetadataInPreview()) {
220             return;
221         }
222 
223         // Set attribution author
224         try {
225             final CharSequence author = info.loadAuthor(pm);
226             if (!TextUtils.isEmpty(author)) {
227                 attributionAuthor.setText(author);
228                 attributionAuthor.setVisibility(View.VISIBLE);
229             }
230         } catch (NotFoundException e) {
231             // It's expected if the live wallpaper doesn't provide this information
232         }
233 
234         // Set attribution description
235         try {
236             final CharSequence description = info.loadDescription(pm);
237             if (!TextUtils.isEmpty(description)) {
238                 attributionDescription.setText(description);
239                 attributionDescription.setVisibility(View.VISIBLE);
240             }
241         } catch (NotFoundException e) {
242             // It's expected if the live wallpaper doesn't provide this information
243         }
244 
245         // Set context information
246         try {
247             final Uri contextUri = info.loadContextUri(pm);
248             if (contextUri != null) {
249                 final Intent intent = new Intent(Intent.ACTION_VIEW, contextUri);
250                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
251                 attributionExploreButton.setOnClickListener(v -> {
252                     try {
253                         startActivity(intent);
254                     } catch (ActivityNotFoundException e) {
255                         Log.e(LOG_TAG, "Couldn't find activity for context link.", e);
256                     }
257                 });
258                 attributionExploreButton.setVisibility(View.VISIBLE);
259                 spacer.setVisibility(View.VISIBLE);
260 
261                 // Update context description string if it's provided
262                 final CharSequence contextDescription = info.loadContextDescription(pm);
263                 if (!TextUtils.isEmpty(contextDescription)) {
264                     attributionExploreButton.setText(contextDescription);
265                 }
266             }
267         } catch (NotFoundException e) {
268             // It's expected if the wallpaper doesn't provide this information
269         }
270     }
271 
272     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
getSettingsSliceUri(@onNull WallpaperInfo info)273     protected Uri getSettingsSliceUri(@NonNull WallpaperInfo info) {
274         return info.getSettingsSliceUri();
275     }
276 
initSettingsPage(WallpaperInfo info)277     private void initSettingsPage(WallpaperInfo info) {
278         final Uri uriSettingsSlice = getSettingsSliceUri(info);
279         if (uriSettingsSlice == null) {
280             return;
281         }
282 
283         final View pageSettings = getLayoutInflater().inflate(R.layout.page_settings,
284                 null /* root */);
285         final Button setWallpaperButton = pageSettings.findViewById(
286                 R.id.preview_attribution_pane_set_wallpaper_button);
287 
288         mSliceViewSettings = pageSettings.findViewById(R.id.settings_slice);
289         mSliceViewSettings.setMode(SliceView.MODE_LARGE);
290         mSliceViewSettings.setScrollable(false);
291 
292         // Set LiveData for SliceView
293         mLiveDataSettings = SliceLiveData.fromUri(this /* context */, uriSettingsSlice);
294         mLiveDataSettings.observeForever(mSliceViewSettings);
295 
296         setWallpaperButton.setOnClickListener(this::setLiveWallpaper);
297 
298         mPages.add(Pair.create(getResources().getString(R.string.tab_customize), pageSettings));
299     }
300 
populateBottomPane()301     private void populateBottomPane() {
302         mViewBottomPane = findViewById(R.id.bottom_pane);
303         mViewPager = findViewById(R.id.viewpager);
304         mTabLayout = findViewById(R.id.tablayout);
305 
306         mBottomSheetBehavior = BottomSheetBehavior.from(mViewBottomPane);
307         mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
308 
309         // Create PagerAdapter
310         final PagerAdapter pagerAdapter = new PagerAdapter() {
311             @NonNull
312             @Override
313             public Object instantiateItem(ViewGroup container, int position) {
314                 final View page = mPages.get(position).second;
315                 container.addView(page);
316                 return page;
317             }
318 
319             @Override
320             public void destroyItem(@NonNull ViewGroup container, int position,
321                     @NonNull Object object) {
322                 if (object instanceof View) {
323                     container.removeView((View) object);
324                 }
325             }
326 
327             @Override
328             public int getCount() {
329                 return mPages.size();
330             }
331 
332             @Override
333             public CharSequence getPageTitle(int position) {
334                 try {
335                     return mPages.get(position).first;
336                 } catch (IndexOutOfBoundsException e) {
337                     return null;
338                 }
339             }
340 
341             @Override
342             public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
343                 return (view == object);
344             }
345         };
346 
347         // Add OnPageChangeListener to re-measure ViewPager's height
348         mViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
349             @Override
350             public void onPageSelected(int position) {
351                 mViewPager.requestLayout();
352             }
353         });
354 
355         // Set PagerAdapter
356         mViewPager.setAdapter(pagerAdapter);
357 
358         // Make TabLayout visible if there are more than one page
359         if (mPages.size() > 1) {
360             mTabLayout.setVisibility(View.VISIBLE);
361             mTabLayout.setupWithViewPager(mViewPager);
362         }
363 
364         // Initializes a rounded rectangle outline and clips the upper corners to be rounded.
365         mViewBottomPane.setOutlineProvider(new ViewOutlineProvider() {
366             private final int radius = getResources().getDimensionPixelSize(
367                     R.dimen.preview_viewpager_round_radius);
368 
369             @Override
370             public void getOutline(View view, Outline outline) {
371                 outline.setRoundRect(0 /* left */, 0 /* top */, view.getWidth(),
372                         view.getHeight() + radius, radius);
373             }
374         });
375         mViewBottomPane.setClipToOutline(true);
376     }
377 
378     @Override
onCreateOptionsMenu(Menu menu)379     public boolean onCreateOptionsMenu(Menu menu) {
380         getMenuInflater().inflate(R.menu.menu_preview, menu);
381         setupPreviewMenu(menu);
382         menu.findItem(R.id.configure).setVisible(mSettingsIntent != null);
383         menu.findItem(R.id.delete_wallpaper).setVisible(mDeleteIntent != null);
384         return super.onCreateOptionsMenu(menu);
385     }
386 
setupPreviewMenu(Menu menu)387     private void setupPreviewMenu(Menu menu) {
388         mPreview = (CheckBox) menu.findItem(R.id.preview).getActionView();
389         mPreview.setOnClickListener(this::setPreviewBehavior);
390 
391         BottomSheetCallback callback = new BottomSheetCallback() {
392             @Override
393             public void onStateChanged(View bottomSheet, int newState) {
394                 switch (newState) {
395                     case BottomSheetBehavior.STATE_COLLAPSED:
396                         setPreviewChecked(true /* checked */);
397                         break;
398                     case BottomSheetBehavior.STATE_EXPANDED:
399                         setPreviewChecked(false /* checked */);
400                         break;
401                 }
402             }
403 
404             @Override
405             public void onSlide(View bottomSheet, float slideOffset) {
406                 mTabLayout.setAlpha(slideOffset);
407                 mViewPager.setAlpha(slideOffset);
408             }
409         };
410         mBottomSheetBehavior.setBottomSheetCallback(callback);
411 
412         int state = mBottomSheetBehavior.getState();
413         callback.onStateChanged(mViewBottomPane, state);
414         switch (state) {
415             case BottomSheetBehavior.STATE_COLLAPSED:
416                 callback.onSlide(mViewBottomPane, 0f);
417                 break;
418             case BottomSheetBehavior.STATE_EXPANDED:
419                 callback.onSlide(mViewBottomPane, 1f);
420                 break;
421         }
422     }
423 
setPreviewChecked(boolean checked)424     private void setPreviewChecked(boolean checked) {
425         if (mPreview != null) {
426             mPreview.setChecked(checked);
427             int resId = checked ? R.string.expand_attribution_panel
428                     : R.string.collapse_attribution_panel;
429             mPreview.setContentDescription(getResources().getString(resId));
430         }
431     }
432 
setPreviewBehavior(final View v)433     private void setPreviewBehavior(final View v) {
434         CheckBox checkbox = (CheckBox) v;
435         if (checkbox.isChecked()) {
436             mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
437         } else {
438             mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
439         }
440     }
441 
setLiveWallpaper(final View v)442     public void setLiveWallpaper(final View v) {
443         if (mWallpaperManager.getWallpaperInfo() != null
444                 && mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_LOCK) < 0) {
445             // The lock screen does not have a distinct wallpaper and the current wallpaper is a
446             // live wallpaper, so since we cannot preserve any static imagery on the lock screen,
447             // set the live wallpaper directly without giving the user a destination option.
448             try {
449                 setLiveWallpaper(v.getRootView().getWindowToken());
450                 setResult(RESULT_OK);
451             } catch (RuntimeException e) {
452                 Log.w(LOG_TAG, "Failure setting wallpaper", e);
453             }
454             finish();
455         } else {
456             // Otherwise, prompt to either set on home or both home and lock screen.
457             final Context themedContext = new ContextThemeWrapper(this /* base */,
458                     android.R.style.Theme_DeviceDefault_Settings);
459             new AlertDialog.Builder(themedContext)
460                     .setTitle(R.string.set_live_wallpaper)
461                     .setAdapter(new WallpaperTargetAdapter(themedContext),
462                             new DialogInterface.OnClickListener() {
463                                 @Override
464                                 public void onClick(DialogInterface dialog, int which) {
465                                     try {
466                                         setLiveWallpaper(v.getRootView().getWindowToken());
467                                         if (which == 1) {
468                                             // "Home screen and lock screen"; clear the lock
469                                             // screen so it
470                                             // shows through to the live wallpaper on home.
471                                             mWallpaperManager.clear(WallpaperManager.FLAG_LOCK);
472                                         }
473                                         setResult(RESULT_OK);
474                                     } catch (RuntimeException | IOException e) {
475                                         Log.w(LOG_TAG, "Failure setting wallpaper", e);
476                                     }
477                                     finish();
478                                 }
479                             })
480                     .show();
481         }
482     }
483 
setLiveWallpaper(IBinder windowToken)484     private void setLiveWallpaper(IBinder windowToken) {
485         mWallpaperManager.setWallpaperComponent(mWallpaperIntent.getComponent());
486         mWallpaperManager.setWallpaperOffsetSteps(0.5f /* xStep */, 0.0f /* yStep */);
487         mWallpaperManager.setWallpaperOffsets(windowToken, 0.5f /* xOffset */, 0.0f /* yOffset */);
488     }
489 
490     @VisibleForTesting
deleteLiveWallpaper()491     void deleteLiveWallpaper() {
492         if (mDeleteIntent != null) {
493             startService(mDeleteIntent);
494             finish();
495         }
496     }
497 
showDeleteConfirmDialog()498     private void showDeleteConfirmDialog() {
499         final AlertDialog alertDialog = new AlertDialog.Builder(this /* context */,
500                 R.style.AlertDialogStyle)
501                 .setMessage(R.string.delete_wallpaper_confirmation)
502                 .setPositiveButton(R.string.delete_live_wallpaper,
503                         (dialog, which) -> deleteLiveWallpaper())
504                 .setNegativeButton(android.R.string.cancel, null /* listener */)
505                 .create();
506         alertDialog.show();
507     }
508 
509     @Override
onOptionsItemSelected(MenuItem item)510     public boolean onOptionsItemSelected(MenuItem item) {
511         int id = item.getItemId();
512         if (id == R.id.configure) {
513             startActivity(mSettingsIntent);
514             return true;
515         } else if (id == R.id.delete_wallpaper) {
516             showDeleteConfirmDialog();
517             return true;
518         } else if (id == android.R.id.home) {
519             onBackPressed();
520             return true;
521         }
522         return super.onOptionsItemSelected(item);
523     }
524 
525     @Override
onResume()526     public void onResume() {
527         super.onResume();
528         if (mWallpaperConnection != null) {
529             mWallpaperConnection.setVisibility(true);
530         }
531     }
532 
533     @Override
onPause()534     public void onPause() {
535         super.onPause();
536         if (mWallpaperConnection != null) {
537             mWallpaperConnection.setVisibility(false);
538         }
539     }
540 
541     @Override
onDestroy()542     protected void onDestroy () {
543         if (mLiveDataSettings != null && mLiveDataSettings.hasObservers()) {
544             mLiveDataSettings.removeObserver(mSliceViewSettings);
545             mLiveDataSettings = null;
546         }
547         if (mWallpaperConnection != null) {
548             mWallpaperConnection.disconnect();
549         }
550         mWallpaperConnection = null;
551         super.onDestroy();
552     }
553 
554     @Override
dispatchTouchEvent(MotionEvent ev)555     public boolean dispatchTouchEvent(MotionEvent ev) {
556         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
557             onUserInteraction();
558         }
559         boolean handled = getWindow().superDispatchTouchEvent(ev);
560         if (!handled) {
561             handled = onTouchEvent(ev);
562         }
563 
564         if (!handled && mWallpaperConnection != null && mWallpaperConnection.mEngine != null) {
565             MotionEvent dup = MotionEvent.obtainNoHistory(ev);
566             try {
567                 mWallpaperConnection.mEngine.dispatchPointer(dup);
568             } catch (RemoteException e) {
569             }
570 
571             int action = ev.getActionMasked();
572             try {
573                 if (action == MotionEvent.ACTION_UP) {
574                     mWallpaperConnection.mEngine.dispatchWallpaperCommand(
575                             WallpaperManager.COMMAND_TAP,
576                             (int) ev.getX(), (int) ev.getY(), 0, null);
577                 } else if (action == MotionEvent.ACTION_POINTER_UP) {
578                     int pointerIndex = ev.getActionIndex();
579                     mWallpaperConnection.mEngine.dispatchWallpaperCommand(
580                             WallpaperManager.COMMAND_SECONDARY_TAP,
581                             (int) ev.getX(pointerIndex), (int) ev.getY(pointerIndex), 0, null);
582                 }
583             } catch (RemoteException e) {
584             }
585         }
586         return handled;
587     }
588 
589     class WallpaperConnection extends IWallpaperConnection.Stub implements ServiceConnection {
590         final Intent mIntent;
591         IWallpaperService mService;
592         IWallpaperEngine mEngine;
593         boolean mConnected;
594         boolean mIsVisible;
595         boolean mIsEngineVisible;
596         WallpaperInfo mInfo;
597 
WallpaperConnection(Intent intent, WallpaperInfo info)598         WallpaperConnection(Intent intent, WallpaperInfo info) {
599             mIntent = intent;
600             mInfo = info;
601         }
602 
connect()603         public boolean connect() {
604             synchronized (this) {
605                 if (!bindService(mIntent, this, Context.BIND_AUTO_CREATE)) {
606                     return false;
607                 }
608 
609                 mConnected = true;
610                 return true;
611             }
612         }
613 
disconnect()614         public void disconnect() {
615             synchronized (this) {
616                 mConnected = false;
617                 if (mEngine != null) {
618                     try {
619                         mEngine.destroy();
620                     } catch (RemoteException e) {
621                         // Ignore
622                     }
623                     mEngine = null;
624                 }
625                 try {
626                     unbindService(this);
627                 } catch (IllegalArgumentException e) {
628                     Log.w(LOG_TAG, "Can't unbind wallpaper service. "
629                             + "It might have crashed, just ignoring.", e);
630                 }
631                 mService = null;
632             }
633         }
634 
635         @Override
onLocalWallpaperColorsChanged(RectF area, WallpaperColors colors, int displayId)636         public void onLocalWallpaperColorsChanged(RectF area,
637                 WallpaperColors colors, int displayId) {
638 
639         }
640 
onServiceConnected(ComponentName name, IBinder service)641         public void onServiceConnected(ComponentName name, IBinder service) {
642             if (mWallpaperConnection == this) {
643                 mService = IWallpaperService.Stub.asInterface(service);
644                 try {
645                     final int displayId = getWindow().getDecorView().getDisplay().getDisplayId();
646                     final View root = getWindow().getDecorView();
647                     mService.attach(this, root.getWindowToken(),
648                             LayoutParams.TYPE_APPLICATION_MEDIA, true, root.getWidth(),
649                             root.getHeight(), new Rect(0, 0, 0, 0), displayId,
650                             WallpaperManager.FLAG_SYSTEM, mInfo);
651                 } catch (RemoteException e) {
652                     Log.w(LOG_TAG, "Failed attaching wallpaper; clearing", e);
653                 }
654             }
655         }
656 
onServiceDisconnected(ComponentName name)657         public void onServiceDisconnected(ComponentName name) {
658             mService = null;
659             mEngine = null;
660             if (mWallpaperConnection == this) {
661                 Log.w(LOG_TAG, "Wallpaper service gone: " + name);
662             }
663         }
664 
attachEngine(IWallpaperEngine engine, int displayId)665         public void attachEngine(IWallpaperEngine engine, int displayId) {
666             synchronized (this) {
667                 if (mConnected) {
668                     mEngine = engine;
669                     if (mIsVisible) {
670                         setEngineVisibility(true);
671                     }
672                 } else {
673                     try {
674                         engine.destroy();
675                     } catch (RemoteException e) {
676                         // Ignore
677                     }
678                 }
679             }
680         }
681 
setWallpaper(String name)682         public ParcelFileDescriptor setWallpaper(String name) {
683             return null;
684         }
685 
686         @Override
onWallpaperColorsChanged(WallpaperColors colors, int displayId)687         public void onWallpaperColorsChanged(WallpaperColors colors, int displayId)
688                 throws RemoteException {
689 
690         }
691 
692         @Override
engineShown(IWallpaperEngine engine)693         public void engineShown(IWallpaperEngine engine) throws RemoteException {
694             mLoading.post(() -> {
695                 mLoading.animate()
696                         .alpha(0f)
697                         .setDuration(220)
698                         .setStartDelay(300)
699                         .setInterpolator(AnimationUtils.loadInterpolator(LiveWallpaperPreview.this,
700                                 android.R.interpolator.fast_out_linear_in))
701                         .withEndAction(() -> mLoading.setVisibility(View.INVISIBLE));
702             });
703         }
704 
setVisibility(boolean visible)705         public void setVisibility(boolean visible) {
706             mIsVisible = visible;
707             setEngineVisibility(visible);
708         }
709 
setEngineVisibility(boolean visible)710         private void setEngineVisibility(boolean visible) {
711             if (mEngine != null && visible != mIsEngineVisible) {
712                 try {
713                     mEngine.setVisibility(visible);
714                     mIsEngineVisible = visible;
715                 } catch (RemoteException e) {
716                     Log.w(LOG_TAG, "Failure setting wallpaper visibility ", e);
717                 }
718             }
719         }
720     }
721 
722     private static class WallpaperTargetAdapter extends ArrayAdapter<CharSequence> {
723 
WallpaperTargetAdapter(Context context)724         public WallpaperTargetAdapter(Context context) {
725             super(context, R.layout.wallpaper_target_dialog_item,
726                     context.getResources().getTextArray(R.array.which_wallpaper_options));
727         }
728 
729         @Override
hasStableIds()730         public boolean hasStableIds() {
731             return true;
732         }
733 
734         @Override
getItemId(int position)735         public long getItemId(int position) {
736             return position;
737         }
738 
739         @Override
getView(int position, View convertView, ViewGroup parent)740         public View getView(int position, View convertView, ViewGroup parent) {
741             TextView tv = (TextView) super.getView(position, convertView, parent);
742             tv.setCompoundDrawablesRelativeWithIntrinsicBounds(
743                     position == 0 ? R.drawable.ic_home : R.drawable.ic_device, 0, 0, 0);
744             return tv;
745         }
746     }
747 }
748