1 // Copyright 2015-present 650 Industries. All rights reserved.
2 package host.exp.exponent
3 
4 import android.Manifest
5 import android.app.Activity
6 import android.app.ActivityManager.RecentTaskInfo
7 import android.content.Intent
8 import android.content.pm.PackageManager
9 import android.os.Build
10 import android.os.Bundle
11 import android.os.Handler
12 import androidx.core.content.ContextCompat
13 import host.exp.exponent.di.NativeModuleDepsProvider
14 import host.exp.exponent.kernel.Kernel
15 import host.exp.expoview.BuildConfig
16 import javax.inject.Inject
17 
18 // This activity is transparent. It uses android:style/Theme.Translucent.NoTitleBar.
19 // Calls finish() once it is done processing Intent.
20 class LauncherActivity : Activity() {
21   @Inject
22   lateinit var kernel: Kernel
23 
24   override fun onCreate(savedInstanceState: Bundle?) {
25     super.onCreate(savedInstanceState)
26     if (BuildConfig.DEBUG) {
27       // Need WRITE_EXTERNAL_STORAGE for method tracing
28       if (Constants.DEBUG_METHOD_TRACING) {
29         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
30           if (ContextCompat.checkSelfPermission(
31               this,
32               Manifest.permission.WRITE_EXTERNAL_STORAGE
33             ) != PackageManager.PERMISSION_GRANTED
34           ) {
35             requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 123)
36           }
37         }
38       }
39     }
40     NativeModuleDepsProvider.instance.inject(LauncherActivity::class.java, this)
41 
42     // Kernel's JS needs to be started for the dev menu to work when the app is launched through the deep link.
43     kernel.startJSKernel(this)
44     kernel.handleIntent(this, intent)
45 
46     // Start a service to keep our process awake. This isn't necessary most of the time, but
47     // if the user has "Don't keep activities" on it's possible for the process to exit in between
48     // finishing this activity and starting the BaseExperienceActivity.
49     startService(ExponentIntentService.getActionStayAwake(applicationContext))
50 
51     // Delay to prevent race condition where finish() is called before service starts.
52     Handler(mainLooper).postDelayed(
53       Runnable {
54         try {
55           // Crash with NoSuchFieldException instead of hard crashing at task.getTaskInfo().numActivities
56           RecentTaskInfo::class.java.getDeclaredField("numActivities")
57           for (task in kernel.tasks) {
58             if (task.taskInfo.id == taskId) {
59               if (task.taskInfo.numActivities == 1) {
60                 finishAndRemoveTask()
61                 return@Runnable
62               } else {
63                 break
64               }
65             }
66           }
67         } catch (e: Exception) {
68           // just go straight to finish()
69         }
70         finish()
71       },
72       100
73     )
74   }
75 
76   public override fun onNewIntent(intent: Intent) {
77     super.onNewIntent(intent)
78 
79     // We shouldn't ever get here, since we call finish() in onCreate. Just want to be safe
80     // since this Activity is singleTask and there might be some edge case where this is called.
81     kernel.handleIntent(this, intent)
82   }
83 }
84