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