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