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