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