LumberyardActivity.java 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. package com.amazon.lumberyard;
  9. import android.app.Activity;
  10. import android.app.AlertDialog;
  11. import android.app.NativeActivity;
  12. import android.content.DialogInterface;
  13. import android.content.Intent;
  14. import android.content.pm.PackageInfo;
  15. import android.content.pm.PackageManager;
  16. import android.content.pm.PackageManager.NameNotFoundException;
  17. import android.content.res.AssetManager;
  18. import android.content.res.Resources;
  19. import android.graphics.Point;
  20. import android.Manifest;
  21. import android.media.AudioManager;
  22. import android.os.Bundle;
  23. import android.os.Looper;
  24. import androidx.core.app.ActivityCompat;
  25. import androidx.core.content.ContextCompat;
  26. import android.util.Log;
  27. import android.view.Display;
  28. import android.view.Gravity;
  29. import android.view.LayoutInflater;
  30. import android.view.View;
  31. import android.view.WindowManager;
  32. import android.widget.LinearLayout;
  33. import android.widget.PopupWindow;
  34. import android.widget.TextView;
  35. import java.io.File;
  36. import java.io.InputStream;
  37. import java.io.IOException;
  38. import java.lang.InterruptedException;
  39. import java.util.ArrayList;
  40. import java.util.List;
  41. import java.util.Random;
  42. import com.amazon.lumberyard.io.APKHandler;
  43. import com.amazon.lumberyard.io.obb.ObbDownloaderActivity;
  44. ////////////////////////////////////////////////////////////////
  45. public class LumberyardActivity extends NativeActivity
  46. {
  47. ////////////////////////////////////////////////////////////////
  48. // Native methods
  49. public static native void nativeOnRequestPermissionsResult(boolean granted);
  50. ////////////////////////////////////////////////////////////////
  51. @Override
  52. public void onBackPressed()
  53. {
  54. // by doing nothing here will prevent the activity from being exited (the default behaviour)
  55. }
  56. ////////////////////////////////////////////////////////////////
  57. // called from the native to get the application package name
  58. // e.g. org.o3de.samples for SamplesProject
  59. public String GetPackageName()
  60. {
  61. return getApplicationContext().getPackageName();
  62. }
  63. ////////////////////////////////////////////////////////////////
  64. // called from the native to get the app version code
  65. // android:versionCode in the AndroidManifest.xml.
  66. public int GetAppVersionCode()
  67. {
  68. try
  69. {
  70. PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
  71. return pInfo.versionCode;
  72. }
  73. catch (NameNotFoundException e)
  74. {
  75. return 0;
  76. }
  77. }
  78. ////////////////////////////////////////////////////////////////
  79. // called from the native code to show the Java splash screen
  80. public void ShowSplashScreen()
  81. {
  82. Log.d(TAG, "ShowSplashScreen called");
  83. this.runOnUiThread(new Runnable() {
  84. @Override
  85. public void run()
  86. {
  87. if (!m_splashShowing)
  88. {
  89. ShowSplashScreenImpl();
  90. }
  91. else
  92. {
  93. Log.d(TAG, "The splash screen is already showing");
  94. }
  95. }
  96. });
  97. }
  98. ////////////////////////////////////////////////////////////////
  99. // called from the native code to dismiss the Java splash screen
  100. public void DismissSplashScreen()
  101. {
  102. Log.d(TAG, "DismissSplashScreen called");
  103. if (m_splashShowing)
  104. {
  105. this.runOnUiThread(new Runnable() {
  106. @Override
  107. public void run()
  108. {
  109. if (m_slashWindow != null)
  110. {
  111. Log.d(TAG, "Dismissing the splash screen");
  112. m_slashWindow.dismiss();
  113. m_slashWindow = null;
  114. }
  115. else
  116. {
  117. Log.d(TAG, "There is no splash screen to dismiss");
  118. }
  119. }
  120. });
  121. m_splashShowing = false;
  122. }
  123. }
  124. ////////////////////////////////////////////////////////////////
  125. public void RegisterActivityResultsListener(ActivityResultsListener listener)
  126. {
  127. m_activityResultsListeners.add(listener);
  128. }
  129. ////////////////////////////////////////////////////////////////
  130. public void UnregisterActivityResultsListener(ActivityResultsListener listener)
  131. {
  132. m_activityResultsListeners.remove(listener);
  133. }
  134. ////////////////////////////////////////////////////////////////
  135. // Starts the download of the obb files and waits (block) until the activity finishes.
  136. // Return true in case of success, false otherwise.
  137. public boolean DownloadObb()
  138. {
  139. Intent downloadIntent = new Intent(this, ObbDownloaderActivity.class);
  140. ActivityResult result = new ActivityResult();
  141. if (launchActivity(downloadIntent, DOWNLOAD_OBB_REQUEST, true, result))
  142. {
  143. return result.m_result == Activity.RESULT_OK;
  144. }
  145. return false;
  146. }
  147. ////////////////////////////////////////////////////////////////
  148. // Returns the value of a boolean resource.
  149. public boolean GetBooleanResource(String resourceName)
  150. {
  151. Resources resources = this.getResources();
  152. int resourceId = resources.getIdentifier(resourceName, "bool", this.getPackageName());
  153. return resources.getBoolean(resourceId);
  154. }
  155. ////////////////////////////////////////////////////////////////
  156. // Request permissions at runtime.
  157. public void RequestPermission(final String permission, final String rationale)
  158. {
  159. if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED)
  160. {
  161. Random rand = new Random();
  162. m_runtimePermissionRequestCode = rand.nextInt(500);
  163. final int requestCode = m_runtimePermissionRequestCode;
  164. if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission))
  165. {
  166. final LumberyardActivity activity = this;
  167. Runnable uiDialog = new Runnable()
  168. {
  169. public void run()
  170. {
  171. AlertDialog.Builder builder = new AlertDialog.Builder(activity);
  172. TextView textView = new TextView(activity);
  173. String title = new String("Reason for requesting " + permission);
  174. textView.setText(title + "\n" + rationale);
  175. builder.setCustomTitle(textView);
  176. builder.setItems(new String[]{"OK"}, new DialogInterface.OnClickListener() {
  177. public void onClick(DialogInterface dialog, int index) {
  178. ActivityCompat.requestPermissions(activity, new String[]{permission}, requestCode);
  179. }
  180. });
  181. AlertDialog dialog = builder.create();
  182. dialog.show();
  183. }
  184. };
  185. activity.runOnUiThread(uiDialog);
  186. }
  187. else
  188. {
  189. ActivityCompat.requestPermissions(this, new String[]{permission}, requestCode);
  190. }
  191. }
  192. else
  193. {
  194. nativeOnRequestPermissionsResult(true);
  195. }
  196. }
  197. // ----
  198. ////////////////////////////////////////////////////////////////
  199. @Override
  200. protected void onCreate(Bundle savedInstanceState)
  201. {
  202. super.onCreate(savedInstanceState);
  203. if (GetBooleanResource("enable_keep_screen_on"))
  204. {
  205. getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
  206. }
  207. ProcessImmersiveModeSetting();
  208. APKHandler.SetAssetManager(getAssets());
  209. AndroidDeviceManager.context = this;
  210. boolean useMainObb = GetBooleanResource("use_main_obb");
  211. boolean usePatchObb = GetBooleanResource("use_patch_obb");
  212. if (AreAssetsInAPK() && (useMainObb || usePatchObb))
  213. {
  214. Log.d(TAG, "Using OBB expansion files for game assets");
  215. File obbRootPath = getApplicationContext().getObbDir();
  216. String packageName = GetPackageName();
  217. int appVersionCode = GetAppVersionCode();
  218. String mainObbFilePath = String.format("%s/main.%d.%s.obb", obbRootPath, appVersionCode, packageName);
  219. String patchObbFilePath = String.format("%s/patch.%d.%s.obb", obbRootPath, appVersionCode, packageName);
  220. File mainObbFile = new File(mainObbFilePath);
  221. File patchObbFile = new File(patchObbFilePath);
  222. boolean needToDownload = ( (useMainObb && !mainObbFile.canRead())
  223. || (usePatchObb && !patchObbFile.canRead()));
  224. if (needToDownload)
  225. {
  226. Log.d(TAG, "Attempting to download the OBB expansion files");
  227. boolean downloadResult = DownloadObb();
  228. if (!downloadResult)
  229. {
  230. Log.e(TAG, "****************************************************************");
  231. Log.e(TAG, "Failed to download the OBB expansion file. Exiting...");
  232. Log.e(TAG, "****************************************************************");
  233. finish();
  234. }
  235. }
  236. }
  237. else
  238. {
  239. Log.d(TAG, "Assets already on the device, not using the OBB expansion files.");
  240. }
  241. // ensure we use the music media stream
  242. setVolumeControlStream(AudioManager.STREAM_MUSIC);
  243. }
  244. ////////////////////////////////////////////////////////////////
  245. @Override
  246. protected void onDestroy()
  247. {
  248. // Signal any thread that is waiting for the result of an activity
  249. for(ActivityResult result : m_waitingResultList)
  250. {
  251. synchronized(result)
  252. {
  253. result.m_isRunning = false;
  254. result.notifyAll();
  255. }
  256. }
  257. // Ideally we should be calling super.onDestroy() here and going through the "graceful" shutdown process,
  258. // however some deadlock(s) happen in the static de-allocation preventing the process to naturally exit.
  259. // On phones, and most tablets, this doesn't happen because the process is terminated by the system but
  260. // while running in Samsung DEX mode it's kept alive until it seemingly exits naturally. Manually killing
  261. // the process in the onDestroy is probably the best compromise until the graceful exit is fixed with LY-70527
  262. android.os.Process.killProcess(android.os.Process.myPid());
  263. }
  264. ////////////////////////////////////////////////////////////////
  265. @Override
  266. public void onWindowFocusChanged(boolean hasFocus)
  267. {
  268. super.onWindowFocusChanged(hasFocus);
  269. if (hasFocus)
  270. {
  271. ProcessImmersiveModeSetting();
  272. }
  273. }
  274. ////////////////////////////////////////////////////////////////
  275. @Override
  276. protected void onActivityResult(int requestCode, int resultCode, Intent data)
  277. {
  278. for (ActivityResultsListener listener : m_activityResultsListeners)
  279. {
  280. listener.ProcessActivityResult(requestCode, resultCode, data);
  281. }
  282. }
  283. ////////////////////////////////////////////////////////////////
  284. @Override
  285. public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults)
  286. {
  287. if (requestCode == m_runtimePermissionRequestCode)
  288. {
  289. if (grantResults.length > 0)
  290. {
  291. if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
  292. {
  293. Log.d(TAG, "Permission Granted");
  294. nativeOnRequestPermissionsResult(true);
  295. }
  296. else
  297. {
  298. Log.d(TAG, "Permission Denied");
  299. nativeOnRequestPermissionsResult(false);
  300. }
  301. }
  302. else
  303. {
  304. // Request was cancelled
  305. nativeOnRequestPermissionsResult(false);
  306. }
  307. m_runtimePermissionRequestCode = -1;
  308. }
  309. }
  310. // ----
  311. ////////////////////////////////////////////////////////////////
  312. private boolean launchActivity(Intent intent, final int activityRequestCode, boolean waitForResult, final ActivityResult result)
  313. {
  314. if (waitForResult)
  315. {
  316. if (Looper.myLooper() == Looper.getMainLooper())
  317. {
  318. // Can't block if we are on the UI Thread.
  319. return false;
  320. }
  321. ActivityResultsListener activityListener = new ActivityResultsListener(this)
  322. {
  323. @Override
  324. public boolean ProcessActivityResult(int requestCode, int resultCode, Intent data)
  325. {
  326. if (requestCode == activityRequestCode)
  327. {
  328. synchronized(result)
  329. {
  330. result.m_result = resultCode;
  331. result.m_isRunning = false;
  332. result.notify();
  333. }
  334. return true;
  335. }
  336. return false;
  337. }
  338. };
  339. this.RegisterActivityResultsListener(activityListener);
  340. m_waitingResultList.add(result);
  341. result.m_isRunning = true;
  342. startActivityForResult(intent, activityRequestCode);
  343. synchronized(result)
  344. {
  345. // Wait until the downloader activity finishes.
  346. boolean ret = true;
  347. while (result.m_isRunning)
  348. {
  349. try
  350. {
  351. result.wait();
  352. }
  353. catch(InterruptedException exception)
  354. {
  355. ret = false;
  356. }
  357. }
  358. this.UnregisterActivityResultsListener(activityListener);
  359. m_waitingResultList.remove(result);
  360. return ret;
  361. }
  362. }
  363. else
  364. {
  365. startActivityForResult(intent, activityRequestCode);
  366. return true;
  367. }
  368. }
  369. ////////////////////////////////////////////////////////////////
  370. private boolean AreAssetsInAPK()
  371. {
  372. try
  373. {
  374. InputStream engine = getAssets().open("engine.json", AssetManager.ACCESS_UNKNOWN);
  375. engine.close();
  376. return true;
  377. }
  378. catch (IOException exception)
  379. {
  380. return false;
  381. }
  382. }
  383. ////////////////////////////////////////////////////////////////
  384. private void ShowSplashScreenImpl()
  385. {
  386. Log.d(TAG, "Showing the Splash Screen");
  387. // load the splash screen view
  388. Resources resources = getResources();
  389. int layoutId = resources.getIdentifier("splash_screen", "layout", getPackageName());
  390. LayoutInflater factory = LayoutInflater.from(this);
  391. View splashView = factory.inflate(layoutId, null);
  392. // get the resolution of the display
  393. Display display = getWindowManager().getDefaultDisplay();
  394. Point size = new Point();
  395. display.getSize(size);
  396. // create the popup with the splash screen layout. this is because the standard
  397. // view hierarchy for Android apps doesn't exist when using the NativeActivity
  398. m_slashWindow = new PopupWindow(splashView, size.x, size.y);
  399. m_slashWindow.setClippingEnabled(false);
  400. // add a dummy layout to the main view for the splash popup window
  401. LinearLayout mainLayout = new LinearLayout(this);
  402. setContentView(mainLayout);
  403. // show the splash window
  404. m_slashWindow.showAtLocation(mainLayout, Gravity.CENTER, 0, 0);
  405. m_slashWindow.update();
  406. m_splashShowing = true;
  407. }
  408. ////////////////////////////////////////////////////////////////
  409. private void ProcessImmersiveModeSetting()
  410. {
  411. int systemUiFlags = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
  412. if (!GetBooleanResource("disable_immersive_mode"))
  413. {
  414. systemUiFlags |= (View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
  415. View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
  416. View.SYSTEM_UI_FLAG_FULLSCREEN |
  417. View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
  418. }
  419. getWindow().getDecorView().setSystemUiVisibility(systemUiFlags);
  420. }
  421. // ----
  422. ////////////////////////////////////////////////////////////////
  423. private class ActivityResult
  424. {
  425. public int m_result;
  426. public boolean m_isRunning;
  427. }
  428. // ----
  429. private static final int DOWNLOAD_OBB_REQUEST = 1337;
  430. private static final String TAG = "LMBR";
  431. private PopupWindow m_slashWindow = null;
  432. private boolean m_splashShowing = false;
  433. private int m_runtimePermissionRequestCode = -1;
  434. private List<ActivityResultsListener> m_activityResultsListeners = new ArrayList<ActivityResultsListener>();
  435. private List<ActivityResult> m_waitingResultList = new ArrayList<ActivityResult>();
  436. }