@@ -46,6 +46,7 @@ import kotlinx.coroutines.flow.flowOf
4646import kotlinx.coroutines.flow.map
4747import kotlinx.coroutines.launch
4848import kotlinx.coroutines.withTimeout
49+ import kotlinx.coroutines.withTimeoutOrNull
4950import timber.log.Timber
5051
5152/* *
@@ -78,103 +79,105 @@ class SystemBridgeAutoStarter @Inject constructor(
7879 @SuppressLint(" NewApi" )
7980 @OptIn(ExperimentalCoroutinesApi ::class )
8081 private val autoStartTypeFlow: Flow <AutoStartType ?> =
81- suAdapter.isRootGranted.flatMapLatest { isRooted ->
82- if (isRooted) {
83- flowOf(AutoStartType .ROOT )
84- } else {
85- val useShizukuFlow =
86- combine(
87- shizukuAdapter.isStarted,
88- permissionAdapter.isGrantedFlow(Permission .SHIZUKU ),
89- ) { isStarted, isGranted ->
90- isStarted && isGranted
91- }
92-
93- useShizukuFlow.flatMapLatest { useShizuku ->
94- if (useShizuku) {
95- flowOf(AutoStartType .SHIZUKU )
96- } else if (buildConfig.sdkInt >= Build .VERSION_CODES .R ) {
97- val isAdbAutoStartAllowed = combine(
98- permissionAdapter.isGrantedFlow(Permission .WRITE_SECURE_SETTINGS ),
99- networkAdapter.isWifiConnected,
100- ) { isWriteSecureSettingsGranted, isWifiConnected ->
101- isWriteSecureSettingsGranted &&
102- isWifiConnected &&
103- setupController.isAdbPaired()
82+ suAdapter.isRootGranted
83+ .filterNotNull()
84+ .flatMapLatest { isRooted ->
85+ if (isRooted) {
86+ flowOf(AutoStartType .ROOT )
87+ } else {
88+ val useShizukuFlow =
89+ combine(
90+ shizukuAdapter.isStarted,
91+ permissionAdapter.isGrantedFlow(Permission .SHIZUKU ),
92+ ) { isStarted, isGranted ->
93+ isStarted && isGranted
10494 }
10595
106- isAdbAutoStartAllowed.distinctUntilChanged()
107- .map { isAdbAutoStartAllowed ->
108- if (isAdbAutoStartAllowed) AutoStartType .ADB else null
109- }.filterNotNull()
110- } else {
111- flowOf(null )
96+ useShizukuFlow.flatMapLatest { useShizuku ->
97+ if (useShizuku) {
98+ flowOf(AutoStartType .SHIZUKU )
99+ } else if (buildConfig.sdkInt >= Build .VERSION_CODES .R ) {
100+ val isAdbAutoStartAllowed = combine(
101+ permissionAdapter.isGrantedFlow(Permission .WRITE_SECURE_SETTINGS ),
102+ networkAdapter.isWifiConnected,
103+ ) { isWriteSecureSettingsGranted, isWifiConnected ->
104+ isWriteSecureSettingsGranted &&
105+ isWifiConnected &&
106+ setupController.isAdbPaired()
107+ }
108+
109+ isAdbAutoStartAllowed.distinctUntilChanged()
110+ .map { isAdbAutoStartAllowed ->
111+ if (isAdbAutoStartAllowed) {
112+ AutoStartType .ADB
113+ } else {
114+ null
115+ }
116+ }.filterNotNull()
117+ } else {
118+ flowOf(null )
119+ }
112120 }
113121 }
114122 }
115- }
116123
117124 /* *
118125 * This emits values when the system bridge needs restarting after it being killed.
119126 */
120127 @OptIn(ExperimentalCoroutinesApi ::class )
121- private val restartFlow : Flow <AutoStartType ?> =
128+ private val autoStartFlow : Flow <AutoStartType ?> =
122129 connectionManager.connectionState.flatMapLatest { connectionState ->
130+
123131 // Do not autostart if it is connected or it was killed from the user
124132 if (connectionState !is SystemBridgeConnectionState .Disconnected ||
125- connectionState.isExpected
133+ connectionState.isStoppedByUser ||
134+ ! getIsUsedBefore() ||
135+ getIsStoppedByUser() ||
136+ isSystemBridgeEmergencyKilled() ||
137+ ! isAutoStartEnabled()
126138 ) {
127139 flowOf(null )
128- } else {
140+ } else if (diedAfterAutostart(connectionState.time)) {
129141 // Do not autostart if the system bridge was killed shortly after.
130142 // This prevents infinite loops happening.
131- if (lastAutoStartTime != null &&
132- connectionState.time - lastAutoStartTime!! < 30000
133- ) {
134- Timber .w(
135- " Not auto starting the system bridge because it was last auto started less than 30 secs ago" ,
136- )
137- showSystemBridgeKilledNotification(
138- getString(R .string.system_bridge_died_notification_not_restarting_text),
139- )
140- flowOf(null )
141- } else {
142- autoStartTypeFlow
143- }
143+ Timber .w(
144+ " Not auto starting the system bridge because it was last auto started less than 5 mins ago" ,
145+ )
146+ showSystemBridgeKilledNotification(
147+ getString(R .string.system_bridge_died_notification_not_restarting_text),
148+ )
149+ flowOf(null )
150+ } else {
151+ autoStartTypeFlow
144152 }
145153 }
146154
147- private var lastAutoStartTime: Long? = null
148-
149155 /* *
150156 * This must only be called once in the application lifecycle
151157 */
152- @OptIn(FlowPreview ::class )
158+ @OptIn(FlowPreview ::class , ExperimentalCoroutinesApi :: class )
153159 fun init () {
154160 coroutineScope.launch {
155- // The Key Mapper process may not necessarily be started on boot due to the
156- // on boot receiver so assume if it is started within a minute of boot that
157- // it should be auto started.
158- val isBoot = clock.elapsedRealtime() <= 300_000
159-
160161 Timber .i(
161- " SystemBridgeAutoStarter init: isBoot= $isBoot " ,
162+ " SystemBridgeAutoStarter init: time since boot= ${clock.elapsedRealtime() / 1000 } seconds " ,
162163 )
163164
164- if (isBoot) {
165- handleAutoStartOnBoot()
166- } else if (BuildConfig .DEBUG && connectionManager.isConnected()) {
165+ // Wait 5 seconds for the system bridge to potentially connect itself to Key Mapper
166+ // before deciding whether to start it.
167+ delay(5000 )
168+
169+ if (BuildConfig .DEBUG && connectionManager.isConnected()) {
167170 // This is useful when developing and need to restart the system bridge
168171 // after making changes to it.
169172 Timber .w(" Restarting system bridge on debug build." )
170173
171174 connectionManager.restartSystemBridge()
172- } else {
173- handleAutoStartFromPreVersion4()
175+ delay(5000 )
174176 }
175177
176- // Only start collecting the restart flow after potentially auto starting it for the first time.
177- restartFlow
178+ handleAutoStartFromPreVersion4()
179+
180+ autoStartFlow
178181 .distinctUntilChanged() // Must come before the filterNotNull
179182 .filterNotNull()
180183 .collectLatest { type ->
@@ -183,48 +186,15 @@ class SystemBridgeAutoStarter @Inject constructor(
183186 }
184187 }
185188
186- private suspend fun handleAutoStartOnBoot () {
187- // Do not autostart if the device was force rebooted. This may be a sign that PRO mode
188- // was broken and the user was trying to reset it.
189- val isCleanShutdown = preferences.get(Keys .isCleanShutdown).map { it ? : false }.first()
190-
191- Timber .i(
192- " SystemBridgeAutoStarter init: isCleanShutdown=$isCleanShutdown " ,
193- )
194-
195- // Reset the value after reading it.
196- preferences.set(Keys .isCleanShutdown, false )
197-
198- val isBootAutoStartEnabled = preferences.get(Keys .isProModeAutoStartBootEnabled)
199- .map { it ? : PreferenceDefaults .PRO_MODE_AUTOSTART_BOOT }
200- .first()
201-
202- // Wait 5 seconds for the system bridge to potentially connect itself to Key Mapper
203- // before starting it.
204- delay(5000 )
205-
206- val connectionState = connectionManager.connectionState.value
207-
208- if (isCleanShutdown &&
209- isBootAutoStartEnabled &&
210- connectionState !is SystemBridgeConnectionState .Connected
211- ) {
212- // Wait a minute so the device can connect to a WiFi network, or a Shizuku
213- // client can automatically connect on boot.
214- Timber .i(" Waiting 60 seconds before auto starting system bridge" )
215- delay(60000 )
216- val autoStartType = autoStartTypeFlow.first()
217-
218- if (autoStartType != null ) {
219- autoStart(autoStartType)
220- }
221- }
222- }
223-
224189 private suspend fun handleAutoStartFromPreVersion4 () {
225- val isFirstTime = preferences.get(Keys .handledRootToProModeUpgrade).first() == null
190+ @Suppress(" DEPRECATION" )
191+ val upgradedFromPreVersion4 = preferences.get(Keys .hasRootPermissionLegacy).first() != null
192+
193+ val isRooted: Boolean = withTimeoutOrNull(1000 ) {
194+ suAdapter.isRootGranted.filterNotNull().first()
195+ } ? : false
226196
227- if (isFirstTime && suAdapter.isRootGranted.value ) {
197+ if (upgradedFromPreVersion4 && isRooted ) {
228198 Timber .i(
229199 " Auto starting system bridge because upgraded from pre version 4.0 and was rooted" ,
230200 )
@@ -235,19 +205,12 @@ class SystemBridgeAutoStarter @Inject constructor(
235205 }
236206
237207 private suspend fun autoStart (type : AutoStartType ) {
238- if (isSystemBridgeEmergencyKilled()) {
239- Timber .w(
240- " Not auto starting the system bridge because it was emergency killed by the user" ,
241- )
242- return
243- }
244-
245208 if (connectionManager.isConnected()) {
246209 Timber .i(" Not auto starting with $type because already connected." )
247210 return
248211 }
249212
250- lastAutoStartTime = clock.elapsedRealtime()
213+ preferences.set( Keys .systemBridgeLastAutoStartTime, clock.elapsedRealtime() )
251214
252215 when (type) {
253216 AutoStartType .ADB -> {
@@ -294,10 +257,33 @@ class SystemBridgeAutoStarter @Inject constructor(
294257 }
295258 }
296259
260+ private suspend fun getIsUsedBefore (): Boolean {
261+ return preferences.get(Keys .isSystemBridgeUsed).first() ? : false
262+ }
263+
264+ private suspend fun getIsStoppedByUser (): Boolean {
265+ return preferences.get(Keys .isSystemBridgeStoppedByUser).first() ? : false
266+ }
267+
297268 private suspend fun isSystemBridgeEmergencyKilled (): Boolean {
298269 return preferences.get(Keys .isSystemBridgeEmergencyKilled).first() == true
299270 }
300271
272+ /* *
273+ * Whether the system bridge died less than 5 minutes after the previous time it was
274+ * auto started.
275+ */
276+ private suspend fun diedAfterAutostart (disconnectionTime : Long ): Boolean {
277+ val lastAutoStartTime = preferences.get(Keys .systemBridgeLastAutoStartTime).first()
278+ return lastAutoStartTime != null && disconnectionTime - lastAutoStartTime < (5 * 60_000 )
279+ }
280+
281+ private suspend fun isAutoStartEnabled (): Boolean {
282+ return preferences.get(Keys .isSystemBridgeKeepAliveEnabled)
283+ .map { it ? : PreferenceDefaults .PRO_MODE_KEEP_ALIVE }
284+ .first()
285+ }
286+
301287 private fun showSystemBridgeKilledNotification (text : String ) {
302288 val model = NotificationModel (
303289 id = ID_SYSTEM_BRIDGE_STATUS ,
0 commit comments