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