1/*
2 * Copyright (C) 2016 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.retaildemo;
18
19import android.app.Activity;
20import android.content.ComponentName;
21import android.content.ContentResolver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.PackageManager;
25import android.content.pm.ResolveInfo;
26import android.database.ContentObserver;
27import android.media.MediaPlayer;
28import android.net.Uri;
29import android.os.Bundle;
30import android.os.Environment;
31import android.os.Handler;
32import android.os.PowerManager;
33import android.os.SystemClock;
34import android.os.UserManager;
35import android.provider.Settings;
36import android.text.TextUtils;
37import android.util.Log;
38import android.view.MotionEvent;
39import android.view.View;
40import android.view.WindowManager;
41import android.widget.VideoView;
42
43import java.io.File;
44
45/**
46 * This is the activity for playing the retail demo video. This will also try to keep
47 * the screen on.
48 *
49 * This will check for the demo video in {@link Environment#getDataPreloadsDemoDirectory()} or
50 * {@link Context#getObbDir()}. If the demo video is not present, it will run a task to download it
51 * from the specified url.
52 */
53public class DemoPlayer extends Activity implements DownloadVideoTask.ResultListener {
54
55    private static final String TAG = "DemoPlayer";
56    private static final boolean DEBUG = false;
57
58    /**
59     * Maximum amount of time to wait for demo user to set up.
60     * After it the user can tap the screen to exit
61     */
62    private static final long READY_TO_TAP_MAX_DELAY_MS = 60 * 1000; // 1 min
63
64    private PowerManager mPowerManager;
65
66    private VideoView mVideoView;
67    private String mDownloadPath;
68    private boolean mUsingDownloadedVideo;
69    private Handler mHandler;
70    private boolean mReadyToTap;
71    private SettingsObserver mSettingsObserver;
72    private File mPreloadedVideoFile;
73
74    @Override
75    protected void onCreate(Bundle savedInstanceState) {
76        super.onCreate(savedInstanceState);
77
78        // Keep screen on
79        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
80                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
81                | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
82        setContentView(R.layout.retail_video);
83
84        mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
85        mHandler = new Handler();
86        final String preloadedFileName = getString(R.string.retail_demo_video_file_name);
87        mPreloadedVideoFile = new File(Environment.getDataPreloadsDemoDirectory(),
88                preloadedFileName);
89        mDownloadPath = getObbDir().getPath() + File.separator + preloadedFileName;
90        mVideoView = (VideoView) findViewById(R.id.video_content);
91
92        // Start playing the video when it is ready
93        mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
94            @Override
95            public void onPrepared(MediaPlayer mediaPlayer) {
96                mediaPlayer.setLooping(true);
97                mVideoView.start();
98            }
99        });
100
101        mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
102            @Override
103            public boolean onError(MediaPlayer mp, int what, int extra) {
104                if (mUsingDownloadedVideo && mPreloadedVideoFile.exists()) {
105                    if (DEBUG) Log.d(TAG, "Error using the downloaded video, "
106                            + "falling back to the preloaded video at " + mPreloadedVideoFile);
107                    mUsingDownloadedVideo = false;
108                    setVideoPath(mPreloadedVideoFile.getPath());
109                    // And delete the downloaded video so that we don't try to use it
110                    // again next time.
111                    new File(mDownloadPath).delete();
112                } else {
113                    displayFallbackView();
114                }
115                return true;
116            }
117        });
118
119        mReadyToTap = isUserSetupComplete();
120        if (!mReadyToTap) {
121            // Wait for setup to finish
122            mSettingsObserver = new SettingsObserver();
123            mSettingsObserver.register();
124            // Allow user to exit the demo player if setup takes too long
125            mHandler.postDelayed(() -> {
126                mReadyToTap = true;
127            }, READY_TO_TAP_MAX_DELAY_MS);
128        }
129
130        loadVideo();
131    }
132
133    private void displayFallbackView() {
134        if (DEBUG) Log.d(TAG, "Showing the fallback view");
135        findViewById(R.id.fallback_layout).setVisibility(View.VISIBLE);
136        findViewById(R.id.video_layout).setVisibility(View.GONE);
137    }
138
139    private void displayVideoView() {
140        if (DEBUG) Log.d(TAG, "Showing the video view");
141        findViewById(R.id.video_layout).setVisibility(View.VISIBLE);
142        findViewById(R.id.fallback_layout).setVisibility(View.GONE);
143    }
144
145    private void loadVideo() {
146        // If the video is already downloaded, then use that and check for an update.
147        // Otherwise check if the video is preloaded, if not download the video from the
148        // specified url.
149        boolean isVideoSet = false;
150        if (new File(mDownloadPath).exists()) {
151            if (DEBUG) Log.d(TAG, "Using the already existing video at " + mDownloadPath);
152            setVideoPath(mDownloadPath);
153            isVideoSet = true;
154        } else if (mPreloadedVideoFile.exists()) {
155            if (DEBUG) Log.d(TAG, "Using the preloaded video at " + mPreloadedVideoFile);
156            setVideoPath(mPreloadedVideoFile.getPath());
157            isVideoSet = true;
158        }
159
160        final String downloadUrl = getString(R.string.retail_demo_video_download_url);
161        // If the download url is empty, then no need to start the download task.
162        if (TextUtils.isEmpty(downloadUrl)) {
163            if (!isVideoSet) {
164                displayFallbackView();
165            }
166            return;
167        }
168        if (!checkIfDownloadingAllowed()) {
169            if (DEBUG) Log.d(TAG, "Downloading not allowed, neither starting download nor checking"
170                    + " for an update.");
171            if (!isVideoSet) {
172                displayFallbackView();
173            }
174            return;
175        }
176        new DownloadVideoTask(this, mDownloadPath, mPreloadedVideoFile, this).run();
177    }
178
179    private boolean checkIfDownloadingAllowed() {
180        final int lastBootCount = DataReaderWriter.readLastBootCount(this);
181        final int bootCount = Settings.Global.getInt(getContentResolver(),
182                Settings.Global.BOOT_COUNT, -1);
183        // Something went wrong, don't do anything.
184        if (bootCount == -1) {
185            return false;
186        }
187        // Error reading the last boot count, just write the current boot count.
188        if (lastBootCount == -1) {
189            DataReaderWriter.writeLastBootCount(this, bootCount);
190            return false;
191        }
192        // We need to download the video atmost once after every boot.
193        if (lastBootCount != bootCount) {
194            DataReaderWriter.writeLastBootCount(this, bootCount);
195            return true;
196        }
197        return false;
198    }
199
200    @Override
201    public void onFileDownloaded(final String filePath) {
202        mUsingDownloadedVideo = true;
203        runOnUiThread(new Runnable() {
204            @Override
205            public void run() {
206                setVideoPath(filePath);
207            }
208        });
209    }
210
211    @Override
212    public void onError() {
213        displayFallbackView();
214    }
215
216    @Override
217    public boolean dispatchTouchEvent(MotionEvent ev) {
218        if (mReadyToTap && getSystemService(UserManager.class).isDemoUser()) {
219            disableSelf();
220        }
221        return true;
222    }
223
224    private void disableSelf() {
225        final String componentName = getString(R.string.demo_overlay_app_component);
226        if (!TextUtils.isEmpty(componentName)) {
227            ComponentName component = ComponentName.unflattenFromString(componentName);
228            if (component != null) {
229                Intent intent = new Intent();
230                intent.setComponent(component);
231                ResolveInfo resolveInfo = getPackageManager().resolveService(intent, 0);
232                if (resolveInfo != null) {
233                    startService(intent);
234                } else {
235                    resolveInfo = getPackageManager().resolveActivity(intent,
236                            PackageManager.MATCH_DEFAULT_ONLY);
237                    if (resolveInfo != null) {
238                        startActivity(intent);
239                    } else {
240                        Log.w(TAG, "Component " + componentName + " cannot be resolved");
241                    }
242                }
243            }
244        }
245        getPackageManager().setComponentEnabledSetting(getComponentName(),
246                PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0);
247    }
248
249    @Override
250    public void onPause() {
251        if (mVideoView != null) {
252            mVideoView.pause();
253        }
254        // If power key is pressed to turn screen off, turn screen back on
255        if (!mPowerManager.isInteractive()) {
256            forceTurnOnScreen();
257        }
258        super.onPause();
259    }
260
261    @Override
262    public void onResume() {
263        super.onResume();
264        // Resume video playing
265        if (mVideoView != null) {
266            mVideoView.start();
267        }
268    }
269
270    @Override
271    protected void onDestroy() {
272        if (mSettingsObserver != null) {
273            mSettingsObserver.unregister();
274            mSettingsObserver = null;
275        }
276        super.onDestroy();
277    }
278
279    @Override
280    public void onWindowFocusChanged(boolean hasFocus) {
281        if (hasFocus) {
282            // Make view fullscreen.
283            // And since flags SYSTEM_UI_FLAG_HIDE_NAVIGATION and SYSTEM_UI_FLAG_HIDE_NAVIGATION
284            // might get cleared on user interaction, we do this here.
285            getWindow().getDecorView().setSystemUiVisibility(
286                    View.SYSTEM_UI_FLAG_LAYOUT_STABLE
287                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
288                            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
289                            | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
290                            | View.SYSTEM_UI_FLAG_FULLSCREEN
291                            | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
292                            | View.STATUS_BAR_DISABLE_BACK);
293        }
294    }
295
296    private void setVideoPath(String videoPath) {
297        // Load the video from resource
298        try {
299            mVideoView.setVideoPath(videoPath);
300            displayVideoView();
301        } catch (Exception e) {
302            Log.e(TAG, "Exception setting video uri! " + e.getMessage());
303            displayFallbackView();
304        }
305    }
306
307    private void forceTurnOnScreen() {
308        final PowerManager.WakeLock wakeLock = mPowerManager.newWakeLock(
309                PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG);
310        wakeLock.acquire();
311        // Device woken up, release the wake-lock
312        wakeLock.release();
313    }
314
315    private class SettingsObserver extends ContentObserver {
316        private final Uri mDemoModeSetupComplete = Settings.Secure.getUriFor(
317                Settings.Secure.DEMO_USER_SETUP_COMPLETE);
318
319        SettingsObserver() {
320            super(mHandler);
321        }
322
323        void register() {
324            ContentResolver cr = getContentResolver();
325            cr.registerContentObserver(mDemoModeSetupComplete, false, this);
326        }
327
328        void unregister() {
329            getContentResolver().unregisterContentObserver(this);
330        }
331
332        @Override
333        public void onChange(boolean selfChange, Uri uri) {
334            if (mDemoModeSetupComplete.equals(uri)) {
335                mReadyToTap = true;
336            }
337        }
338    }
339
340    private boolean isUserSetupComplete() {
341        return "1".equals(Settings.Secure.getString(getContentResolver(),
342                Settings.Secure.DEMO_USER_SETUP_COMPLETE));
343    }
344}
345