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