Showing preview only (225K chars total). Download the full file or copy to clipboard to get everything.
Repository: endyrubbin/AAStream
Branch: master
Commit: 13806b628264
Files: 97
Total size: 199.9 KB
Directory structure:
gitextract_rg_4i2rc/
├── README.md
├── apk_releases/
│ ├── aa-stream-v1.0.6.apk
│ └── aa-stream-v1.1.0.27.apk
├── app/
│ ├── build.gradle
│ ├── libs/
│ │ └── aauto.aar
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── assets/
│ │ └── libs/
│ │ ├── arm64-v8a/
│ │ │ ├── minitouch
│ │ │ └── minitouch-nopie
│ │ ├── armeabi/
│ │ │ ├── minitouch
│ │ │ └── minitouch-nopie
│ │ ├── armeabi-v7a/
│ │ │ ├── minitouch
│ │ │ └── minitouch-nopie
│ │ ├── mips/
│ │ │ ├── minitouch
│ │ │ └── minitouch-nopie
│ │ ├── mips64/
│ │ │ ├── minitouch
│ │ │ └── minitouch-nopie
│ │ ├── x86/
│ │ │ ├── minitouch
│ │ │ └── minitouch-nopie
│ │ └── x86_64/
│ │ ├── minitouch
│ │ └── minitouch-nopie
│ ├── java/
│ │ └── com/
│ │ └── garage/
│ │ └── aastream/
│ │ ├── App.kt
│ │ ├── activities/
│ │ │ ├── CarDebugActivity.kt
│ │ │ ├── CarMainActivity.kt
│ │ │ ├── KillerActivity.kt
│ │ │ ├── ResultRequestActivity.kt
│ │ │ ├── SettingsActivity.kt
│ │ │ └── controllers/
│ │ │ ├── CarActivityController.kt
│ │ │ └── TerminalController.kt
│ │ ├── adapters/
│ │ │ └── AppListAdapter.kt
│ │ ├── handlers/
│ │ │ ├── AppHandler.kt
│ │ │ ├── AudioHandler.kt
│ │ │ ├── BrightnessHandler.kt
│ │ │ ├── DisplayHandler.kt
│ │ │ ├── NotificationHandler.kt
│ │ │ ├── PreferenceHandler.kt
│ │ │ └── RotationHandler.kt
│ │ ├── injection/
│ │ │ ├── GlideModule.kt
│ │ │ ├── InjectionComponent.kt
│ │ │ └── InjectionModule.kt
│ │ ├── interfaces/
│ │ │ ├── OnAppClickedCallback.kt
│ │ │ ├── OnAppListLoadedCallback.kt
│ │ │ ├── OnLogCallback.kt
│ │ │ ├── OnMenuTapCallback.kt
│ │ │ ├── OnMinitouchCallback.kt
│ │ │ ├── OnPatchStatusCallback.kt
│ │ │ ├── OnRotationChangedCallback.kt
│ │ │ └── OnScreenLockCallback.kt
│ │ ├── minitouch/
│ │ │ ├── MiniTouchHandler.kt
│ │ │ ├── MiniTouchSocket.kt
│ │ │ └── MinitouchDaemon.kt
│ │ ├── models/
│ │ │ ├── AppItem.kt
│ │ │ └── AppItemWrapper.kt
│ │ ├── receivers/
│ │ │ ├── ScreenLockReceiver.kt
│ │ │ └── UsbStateReceiver.kt
│ │ ├── services/
│ │ │ └── CarService.kt
│ │ ├── shell/
│ │ │ └── ShellExecutor.kt
│ │ ├── utils/
│ │ │ ├── Const.kt
│ │ │ ├── DevLog.kt
│ │ │ └── PhenotypePatcher.kt
│ │ └── views/
│ │ ├── AutoFitRecyclerView.kt
│ │ ├── FingerTapDetector.kt
│ │ └── MarginDecoration.kt
│ └── res/
│ ├── drawable-anydpi/
│ │ ├── bg.xml
│ │ ├── bg_input.xml
│ │ ├── ic_apps.xml
│ │ ├── ic_back.xml
│ │ ├── ic_bookmark.xml
│ │ ├── ic_bookmark_border.xml
│ │ ├── ic_check.xml
│ │ ├── ic_close.xml
│ │ └── ic_terminal.xml
│ ├── layout/
│ │ ├── activity_car.xml
│ │ ├── activity_request_result.xml
│ │ ├── activity_settings.xml
│ │ ├── row_app_item.xml
│ │ ├── view_car_terminal.xml
│ │ ├── view_settings_about.xml
│ │ ├── view_settings_audio.xml
│ │ ├── view_settings_brightness.xml
│ │ ├── view_settings_debug.xml
│ │ ├── view_settings_immersive.xml
│ │ ├── view_settings_resize.xml
│ │ ├── view_settings_rotation.xml
│ │ ├── view_settings_sidebar.xml
│ │ └── view_settings_unlock.xml
│ ├── raw/
│ │ └── sqlite3
│ ├── values/
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── ids.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── xml/
│ └── automotive_app_desc.xml
├── build.gradle
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: README.md
================================================
# AA Stream

## About
**AA Stream** is an unofficial and unsupported device screen mirroring application inspired by [AAMirror](https://github.com/slashmax/AAMirror) for Android Auto.
**Use it with caution! I don't take any responsibility for the misuse of the application. You have been warned.**
Get the latest APK [here](https://github.com/endyrubbin/AAStream/tree/master/apk_releases)
## Prerequisites and usage
#### To use this application:
- Your device has to be **rooted** (You have to figure out how to do it for yourself).
- Android Auto must be installed, preferably an older version (~v3).
- Write System settings must be granted (Enable the switch for **AA Stream** once prompted) to use brightness and rotation features.
- Screen Capture permission granted (Allow it when prompted).
#### Enable Developer Mode in Android Auto.
- Install and open the `Android Auto` App
- Select the `About` section from menu.
- Click the Header `About Android Auto` a few times, until the dev mode is turned on.
- Click the `Menu` (3 dots) button and open `Developer Settings`.
- Set the `Application Mode` to `Developer`.
- Scroll down and ensure `Unknown sources` is checked.
#### Whitelist AA Stream for Android Auto
- Open **AA Stream**.
- Click on `Unlock for Android Auto`.
- If green check mark and a Toast message with success is shown - you are good to go. (If not - ensure your device is rooted).
- Restart device for the changes to take effect.
- Connect the device to your car and select **AA Stream** from all apps menu (Last icon on the right in cars display), if **AA Stream** is not there, redo the steps.
## Settings Activity guide
<img align="right" src="img/settings.gif" alt="Settings Activity" height="500">
- **Unlock for Android Auto**
- Click here to whitelist **AA Stream** for Android Auto. Root permission is required!
- **AA Stream** is successfully unlocked if a green check mark is visible.
- **Overwrite screen brightness**
- Enable this setting to override device brightness when **AA Stream** is started from Android Auto.
- Use this to save device battery as the device screen needs to be always on to mirror it in cars display.
- **Force screen rotation**
- Enable this setting to force the device screen to be rotated to predefined degrees (0, 90, 180, 270).
- Use this to start apps in landscape mode (If the app supports it).
- **Force screen resizing**
- Enable this setting to force the device screen to be resized to match car display density.
- **Force immersive mode**
- Enable this setting to force immersive mode (Hide device status bar).
- **Force audio focus**
- Enable this setting to force audio focus when **AA Stream** is launched.
- **Show sidebar on startup**
- Enable this to show sidebar menu on **AA Stream** startup.
- Choose which menu option should be shown when sidebar is opened.
- Set it to `Favorites` to show your favorite apps (To add or remove an app to favorites, press and hold an app icon for few seconds).
- Choose how to open sidebar menu, wit two finger tap, double or triple taps.
- **About**
- Click on the app icon for 10 times to enable debug mode.
- This adds a new option in car display to see the logs of the app.
## Car Activity guide
<img src="img/android_auto.gif" alt="Car Activity" height="500">
<br/>
- **Menu close button**
- Click here to close the sidebar.
- **Menu back button**
- Click here to send back press command to the device.
- **Menu app drawer button**
- Click here to show all apps available in your device.
- Long press an app icon to add or remove the app to your favorites.
- **Menu favorite apps button**
- Click here to list all your favorite apps.
- Long click on an app icon to remove it from your favorites.
- **Menu debug button**
- Visible only if debug mode is enabled.
- Shows all app logs in real time for debugging.
## Credits
- Inspired by: [AAMirror](https://github.com/slashmax/AAMirror)
- Whitelist queries taken from: [AA-Phenotype-Patcher](https://github.com/Eselter/AA-Phenotype-Patcher)
- Wouldn't be possible without: [AAuto-SDK](https://github.com/martoreto/aauto-sdk)
================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
def getVersionCode = { ->
try {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-list', 'HEAD', '--count'
standardOutput = stdout
}
return Integer.parseInt(stdout.toString().trim())
}
catch (ignored) {
return -1
}
}
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.garage.aastream"
minSdkVersion 21
targetSdkVersion 28
versionCode getVersionCode()
versionName "1.1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
archivesBaseName = "aa-stream-v$versionName.$versionCode"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.aar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.core:core-ktx:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'eu.chainfire:libsuperuser:1.1.0.201903290044'
implementation 'com.google.dagger:dagger:2.21'
annotationProcessor 'com.google.dagger:dagger-compiler:2.21'
kapt 'com.google.dagger:dagger-compiler:2.21'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
kapt 'com.github.bumptech.glide:glide:4.9.0'
kapt "com.github.bumptech.glide:compiler:4.9.0"
implementation 'com.github.bumptech.glide:glide:4.9.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
}
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dist="http://schemas.android.com/apk/distribution"
xmlns:tools="http://schemas.android.com/tools"
package="com.garage.aastream">
<dist:module dist:instant="true"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.ACCESS_SUPERUSER"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<!-- System permissions - granted for root -->
<uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" tools:ignore="ProtectedPermissions"/>
<uses-permission android:name="android.permission.CAPTURE_SECURE_VIDEO_OUTPUT" tools:ignore="ProtectedPermissions"/>
<uses-permission android:name="android.permission.WRITE_SETTINGS" tools:ignore="ProtectedPermissions"/>
<application
android:name="com.garage.aastream.App"
android:allowBackup="true"
android:fullBackupContent="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:hardwareAccelerated="true"
tools:ignore="GoogleAppIndexingWarning">
<activity
android:name="com.garage.aastream.activities.CarDebugActivity"
android:theme="@style/AppTheme.NoActionBar"/>
<activity
android:name="com.garage.aastream.activities.ResultRequestActivity"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<activity android:name="com.garage.aastream.activities.SettingsActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".activities.KillerActivity"
android:screenOrientation="portrait"
android:configChanges="screenSize|density|orientation|screenLayout"
android:launchMode="singleTop"
/>
<service
android:name="com.garage.aastream.services.CarService"
android:enabled="true"
android:exported="true"
android:label="@string/app_name"
tools:ignore="ExportedService">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.google.android.gms.car.category.CATEGORY_PROJECTION" />
<category android:name="com.google.android.gms.car.category.CATEGORY_PROJECTION_OEM" />
</intent-filter>
</service>
<meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />
</application>
</manifest>
================================================
FILE: app/src/main/java/com/garage/aastream/App.kt
================================================
package com.garage.aastream
import android.app.Application
import android.content.res.Configuration
import com.garage.aastream.injection.DaggerInjectionComponent
import com.garage.aastream.injection.InjectionComponent
import com.garage.aastream.injection.InjectionModule
import com.garage.aastream.interfaces.OnRotationChangedCallback
import com.garage.aastream.utils.DevLog
/**
* Created by Endy Rubbin on 22.05.2019 10:54.
* For project: AAStream
*/
@Suppress("unused")
class App : Application() {
lateinit var component: InjectionComponent
private var callback: OnRotationChangedCallback? = null
override fun onCreate() {
super.onCreate()
DevLog.init(getString(R.string.app_name), BuildConfig.DEBUG)
component = DaggerInjectionComponent.builder().injectionModule(InjectionModule(this)).build()
component.inject(this)
}
override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
DevLog.d("Configuration changed")
callback?.onRotationChanged()
}
fun setRotationCallback(callback: OnRotationChangedCallback) {
this.callback = callback
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/activities/CarDebugActivity.kt
================================================
package com.garage.aastream.activities
import android.content.res.Configuration
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.garage.aastream.App
import com.garage.aastream.R
import com.garage.aastream.activities.controllers.CarActivityController
import com.garage.aastream.utils.DevLog
import javax.inject.Inject
/**
* Created by Endy Rubbin on 22.05.2019 10:44.
* For project: AAStream
*/
class CarDebugActivity : AppCompatActivity() {
@Inject lateinit var activityController: CarActivityController
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.AppTheme_NoActionBar)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_car)
(application as App).component.inject(this)
DevLog.d("Car debug activity created")
activityController.onCreate(window.decorView, windowManager)
}
override fun onResume() {
super.onResume()
activityController.onResume()
}
override fun onStart() {
super.onStart()
activityController.onStart()
}
override fun onStop() {
super.onStop()
activityController.onStop()
}
override fun onDestroy() {
super.onDestroy()
activityController.onDestroy()
}
override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
activityController.onConfigurationChanged()
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/activities/CarMainActivity.kt
================================================
package com.garage.aastream.activities
import android.content.res.Configuration
import android.os.Bundle
import com.garage.aastream.App
import com.garage.aastream.R
import com.garage.aastream.activities.controllers.CarActivityController
import com.garage.aastream.utils.DevLog
import com.google.android.apps.auto.sdk.CarActivity
import javax.inject.Inject
/**
* Created by Endy Rubbin on 22.05.2019 10:44.
* For project: AAStream
*/
class CarMainActivity : CarActivity() {
@Inject lateinit var activityController: CarActivityController
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.AppTheme_NoActionBar)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_car)
(applicationContext as App).component.inject(this)
(applicationContext as App).setRotationCallback(activityController)
DevLog.d("Car main activity created")
activityController.onCreate(c().decorView, c().windowManager, carUiController)
@Suppress("DEPRECATION")
setIgnoreConfigChanges(0xFFFF)
}
override fun onResume() {
super.onResume()
activityController.onResume()
}
override fun onStart() {
super.onStart()
activityController.onStart()
}
override fun onStop() {
super.onStop()
activityController.onStop()
}
override fun onDestroy() {
super.onDestroy()
activityController.onDestroy()
}
override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
activityController.onConfigurationChanged()
}
override fun onWindowFocusChanged(focus: Boolean, b1: Boolean) {
super.onWindowFocusChanged(focus, b1)
DevLog.d("Window focus changed $focus")
if (focus) {
activityController.onWindowFocusChanged()
}
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/activities/KillerActivity.kt
================================================
package com.garage.aastream.activities
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.garage.aastream.App
import com.garage.aastream.handlers.BrightnessHandler
import com.garage.aastream.handlers.DisplayHandler
import com.garage.aastream.handlers.NotificationHandler
import com.garage.aastream.handlers.RotationHandler
import com.garage.aastream.utils.DevLog
import javax.inject.Inject
/**
* Created by Endy Rubbin on 10.06.2019 15:37.
* For project: AAStream
*/
class KillerActivity: AppCompatActivity() {
@Inject lateinit var brightnessHandler: BrightnessHandler
@Inject lateinit var rotationHandler: RotationHandler
@Inject lateinit var notificationHandler: NotificationHandler
@Inject lateinit var displayHandler: DisplayHandler
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
(application as App).component.inject(this)
DevLog.d("Killer Activity launched")
intent.removeExtra(NotificationHandler.ACTION_EXIT)
notificationHandler.clearNotification()
displayHandler.restoreDisplaySettings()
brightnessHandler.restoreScreenBrightness()
rotationHandler.restoreScreenRotation()
DevLog.d("AAStream values reset - finishing app")
finishAffinity()
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/activities/ResultRequestActivity.kt
================================================
package com.garage.aastream.activities
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Message
import androidx.appcompat.app.AppCompatActivity
import com.garage.aastream.R
import com.garage.aastream.utils.DevLog
/**
* Created by Endy Rubbin on 27.05.2019 16:11.
* For project: AAStream
*/
class ResultRequestActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
DevLog.d("Request onCreate")
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_request_result)
startActivityForResult()
}
override fun onDestroy() {
DevLog.d("Request onDestroy")
super.onDestroy()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
DevLog.d("Request onActivityResult")
super.onActivityResult(requestCode, resultCode, data)
if (resultHandler != null) {
val msg = Message.obtain(resultHandler, requestWhat, requestCode, resultCode, data)
msg.sendToTarget()
}
finish()
}
private fun startActivityForResult() {
DevLog.d("Request startActivityForResult")
if (resultHandler != null && requestIntent != null) {
startActivityForResult(requestIntent, requestCode)
} else {
finish()
}
}
companion object {
private var resultHandler: Handler? = null
private var requestWhat: Int = 0
private var requestIntent: Intent? = null
private var requestCode: Int = 0
fun startActivityForResult(context: Context, handler: Handler, what: Int, intent: Intent, requestCod: Int) {
DevLog.d("startActivityForResult")
resultHandler = handler
requestWhat = what
requestIntent = intent
requestCode = requestCod
val request = Intent(context, ResultRequestActivity::class.java)
request.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(request)
}
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/activities/SettingsActivity.kt
================================================
package com.garage.aastream.activities
import android.annotation.TargetApi
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.provider.Settings.ACTION_MANAGE_WRITE_SETTINGS
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.SeekBar
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.garage.aastream.App
import com.garage.aastream.BuildConfig
import com.garage.aastream.R
import com.garage.aastream.handlers.BrightnessHandler
import com.garage.aastream.handlers.PreferenceHandler
import com.garage.aastream.handlers.RotationHandler
import com.garage.aastream.interfaces.OnPatchStatusCallback
import com.garage.aastream.utils.Const
import com.garage.aastream.utils.DevLog
import com.garage.aastream.utils.PhenotypePatcher
import kotlinx.android.synthetic.main.activity_settings.*
import kotlinx.android.synthetic.main.view_settings_about.*
import kotlinx.android.synthetic.main.view_settings_audio.*
import kotlinx.android.synthetic.main.view_settings_brightness.*
import kotlinx.android.synthetic.main.view_settings_debug.*
import kotlinx.android.synthetic.main.view_settings_immersive.*
import kotlinx.android.synthetic.main.view_settings_resize.*
import kotlinx.android.synthetic.main.view_settings_rotation.*
import kotlinx.android.synthetic.main.view_settings_sidebar.*
import kotlinx.android.synthetic.main.view_settings_unlock.*
import javax.inject.Inject
/**
* Created by Endy Rubbin on 22.05.2019 10:44.
* For project: AAStream
*/
class SettingsActivity : AppCompatActivity() {
@Inject lateinit var preferences: PreferenceHandler
@Inject lateinit var brightnessHandler: BrightnessHandler
@Inject lateinit var rotationHandler: RotationHandler
@Inject lateinit var patcher: PhenotypePatcher
private var previousTime: Long = 0
private var count = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
(application as App).component.inject(this)
initViews()
}
/**
* Check if app can modify System settings
*/
@TargetApi(Build.VERSION_CODES.M)
private fun checkForSystemWritePermission() {
if (!Settings.System.canWrite(this)) {
startActivity(Intent(ACTION_MANAGE_WRITE_SETTINGS))
}
}
/**
* Initialize views and set listeners
*/
private fun initViews() {
// Debug controller
settings_debug_activity_holder.setOnClickListener {
startActivity(Intent(this, CarDebugActivity::class.java))
}
settings_debug_switch.setOnCheckedChangeListener { _, isChecked ->
DevLog.d("Debug switch changed: $isChecked")
preferences.putBoolean(PreferenceHandler.KEY_DEBUG_DISABLED, isChecked)
view_settings_debug.visibility = if (isChecked) View.GONE else View.VISIBLE
}
settings_debug_switch.isChecked = false
view_settings_debug.visibility = if (preferences.getBoolean(PreferenceHandler.KEY_DEBUG_DISABLED, true)) {
View.GONE
} else {
View.VISIBLE
}
// Unlock controller
view_settings_unlock.setOnClickListener { unlock() }
settings_unlock_state_icon.visibility = if (patcher.isPatched()) View.VISIBLE else View.GONE
// Brightness controller
settings_brightness_seek_bar.progress = brightnessHandler.getScreenBrightness()
settings_brightness_seek_bar.max = Const.MAX_VALUE
settings_brightness_seek_bar.setOnSeekBarChangeListener(object: SeekBar.OnSeekBarChangeListener {
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
DevLog.d("Brightness value changed $progress")
preferences.putInt(PreferenceHandler.KEY_BRIGHTNESS_VALUE, progress)
}
})
settings_brightness_switch.isChecked = preferences.getBoolean(PreferenceHandler.KEY_BRIGHTNESS_SWITCH, false)
settings_brightness_switch.setOnCheckedChangeListener { _, isChecked ->
DevLog.d("Brightness switch changed: $isChecked")
if (isChecked) {
checkForSystemWritePermission()
}
preferences.putBoolean(PreferenceHandler.KEY_BRIGHTNESS_SWITCH, isChecked)
settings_brightness_seek_bar.isEnabled = isChecked
}
settings_brightness_seek_bar.isEnabled = settings_brightness_switch.isChecked
// Rotation controller
val rotationAdapter = ArrayAdapter.createFromResource(this, R.array.rotation_values,
android.R.layout.simple_spinner_item)
rotationAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
settings_rotation_dropdown.adapter = rotationAdapter
settings_rotation_dropdown.setSelection(rotationHandler.getScreenRotation())
settings_rotation_dropdown.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
DevLog.d("Rotation selected $position")
preferences.putInt(PreferenceHandler.KEY_ROTATION_VALUE, position)
}
}
settings_rotation_switch.isChecked = preferences.getBoolean(PreferenceHandler.KEY_ROTATION_SWITCH, false)
settings_rotation_switch.setOnCheckedChangeListener { _, isChecked ->
DevLog.d("Rotation switch changed: $isChecked")
if (isChecked) {
checkForSystemWritePermission()
}
preferences.putBoolean(PreferenceHandler.KEY_ROTATION_SWITCH, isChecked)
settings_rotation_dropdown.isEnabled = isChecked
}
settings_rotation_dropdown.isEnabled = settings_rotation_switch.isChecked
// Resize controller
settings_resize_switch.isChecked = preferences.getBoolean(PreferenceHandler.KEY_RESIZE_ENABLED, false)
settings_resize_switch.setOnCheckedChangeListener { _, isChecked ->
DevLog.d("Resize switch changed: $isChecked")
if (isChecked) {
checkForSystemWritePermission()
}
preferences.putBoolean(PreferenceHandler.KEY_RESIZE_ENABLED, isChecked)
}
// Immersive controller
settings_immersive_switch.isChecked = preferences.getBoolean(PreferenceHandler.KEY_IMMERSIVE_MODE, false)
settings_immersive_switch.setOnCheckedChangeListener { _, isChecked ->
DevLog.d("Immersive switch changed: $isChecked")
if (isChecked) {
checkForSystemWritePermission()
}
preferences.putBoolean(PreferenceHandler.KEY_IMMERSIVE_MODE, isChecked)
}
// Audio controller
settings_audio_switch.isChecked = preferences.getBoolean(PreferenceHandler.KEY_AUDIO_FOCUS, false)
settings_audio_switch.setOnCheckedChangeListener { _, isChecked ->
DevLog.d("Audio switch changed: $isChecked")
if (isChecked) {
checkForSystemWritePermission()
}
preferences.putBoolean(PreferenceHandler.KEY_AUDIO_FOCUS, isChecked)
}
// Sidebar controller
val sidebarAdapter = ArrayAdapter.createFromResource(this, R.array.screen_values,
android.R.layout.simple_spinner_item)
sidebarAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
settings_sidebar_dropdown.adapter = sidebarAdapter
settings_sidebar_dropdown.setSelection(preferences.getInt(PreferenceHandler.KEY_STARTUP_VALUE,
Const.DEFAULT_SCREEN))
settings_sidebar_dropdown.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
DevLog.d("Startup screen selected $position")
preferences.putInt(PreferenceHandler.KEY_STARTUP_VALUE, position)
}
}
val sidebarMenuAdapter = ArrayAdapter.createFromResource(this, R.array.tap_values,
android.R.layout.simple_spinner_item)
sidebarMenuAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
settings_sidebar_dropdown_menu.adapter = sidebarMenuAdapter
settings_sidebar_dropdown_menu.setSelection(preferences.getInt(PreferenceHandler.KEY_OPEN_MENU_METHOD,
Const.DEFAULT_TAP_METHOD))
settings_sidebar_dropdown_menu.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
DevLog.d("Sidebar open method selected $position")
preferences.putInt(PreferenceHandler.KEY_OPEN_MENU_METHOD, position)
}
}
settings_sidebar_switch.isChecked = preferences.getBoolean(PreferenceHandler.KEY_SIDEBAR_SWITCH,
Const.DEFAULT_SHOW_SIDEBAR)
settings_sidebar_switch.setOnCheckedChangeListener { _, isChecked ->
DevLog.d("Sidebar switch changed: $isChecked")
preferences.putBoolean(PreferenceHandler.KEY_SIDEBAR_SWITCH, isChecked)
}
// About controller
settings_about_version.text = getString(R.string.txt_version,
"${BuildConfig.VERSION_NAME}.${BuildConfig.VERSION_CODE}")
settings_about.setOnClickListener {
val currentTime = System.currentTimeMillis()
if (currentTime - previousTime <= Const.CLICK_INTERVAL) {
count++
} else {
count = 0
}
previousTime = currentTime
if (count == Const.DEBUG_CLICK_COUNT) {
DevLog.d("Debug mode enabled")
Toast.makeText(this@SettingsActivity, getString(R.string.toast_developer_mode_enabled),
Toast.LENGTH_LONG).show()
preferences.putBoolean(PreferenceHandler.KEY_DEBUG_DISABLED, false)
view_settings_debug.visibility = View.VISIBLE
settings_debug_switch.isChecked = false
} else if (count >= Const.DEBUG_CLICK_COUNT - 3 && count < Const.DEBUG_CLICK_COUNT) {
Toast.makeText(this@SettingsActivity, getString(R.string.toast_developer_mode_click,
(Const.DEBUG_CLICK_COUNT - count)), Toast.LENGTH_SHORT).show()
}
}
}
/**
* White list this app for Android Auto
* Reference: @see <a href="https://github.com/Eselter/AA-Phenotype-Patcher">AA-Phenotype-Patcher</a>
*/
private fun unlock() {
settings_unlock_state_spinner.visibility = View.VISIBLE
settings_unlock_state_icon.visibility = View.GONE
patcher.patch(object : OnPatchStatusCallback{
override fun onPatchSuccessful() {
runOnUiThread {
Toast.makeText(this@SettingsActivity,
getString(R.string.toast_app_whitelisted), Toast.LENGTH_LONG).show()
settings_unlock_state_icon.visibility = View.VISIBLE
settings_unlock_state_spinner.visibility = View.GONE
}
}
override fun onPatchFailed() {
runOnUiThread {
Toast.makeText(this@SettingsActivity,
getString(R.string.toast_root_not_available), Toast.LENGTH_LONG).show()
settings_unlock_state_icon.visibility = View.GONE
settings_unlock_state_spinner.visibility = View.GONE
}
}
})
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/activities/controllers/CarActivityController.kt
================================================
package com.garage.aastream.activities.controllers
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.app.Application
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.Color
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.hardware.usb.UsbManager
import android.media.projection.MediaProjection
import android.media.projection.MediaProjectionManager
import android.os.Handler
import android.os.Looper
import android.os.PowerManager
import android.os.PowerManager.ACQUIRE_CAUSES_WAKEUP
import android.os.PowerManager.SCREEN_DIM_WAKE_LOCK
import android.util.DisplayMetrics
import android.view.KeyEvent
import android.view.OrientationEventListener
import android.view.View
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import com.garage.aastream.App
import com.garage.aastream.R
import com.garage.aastream.activities.ResultRequestActivity
import com.garage.aastream.adapters.AppListAdapter
import com.garage.aastream.handlers.*
import com.garage.aastream.interfaces.*
import com.garage.aastream.minitouch.MiniTouchHandler
import com.garage.aastream.minitouch.MinitouchDaemon
import com.garage.aastream.models.AppItem
import com.garage.aastream.receivers.ScreenLockReceiver
import com.garage.aastream.receivers.UsbStateReceiver
import com.garage.aastream.receivers.UsbStateReceiver.UsbStateCallback
import com.garage.aastream.shell.ShellExecutor
import com.garage.aastream.utils.Const
import com.garage.aastream.utils.DevLog
import com.garage.aastream.views.MarginDecoration
import com.google.android.apps.auto.sdk.CarUiController
import com.google.android.apps.auto.sdk.DayNightStyle
import eu.chainfire.libsuperuser.Shell
import kotlinx.android.synthetic.main.activity_car.view.*
import kotlinx.android.synthetic.main.view_car_terminal.view.*
import javax.inject.Inject
/**
* Created by Endy Rubbin on 28.05.2019 10:54.
* For project: AAStream
*/
class CarActivityController(val context: Application) : OnScreenLockCallback, OnAppClickedCallback,
OnAppListLoadedCallback, OnMenuTapCallback, OnRotationChangedCallback, OnMinitouchCallback, UsbStateCallback {
@Inject lateinit var appHandler: AppHandler
@Inject lateinit var preferences: PreferenceHandler
@Inject lateinit var brightnessHandler: BrightnessHandler
@Inject lateinit var rotationHandler: RotationHandler
@Inject lateinit var miniTouchHandler: MiniTouchHandler
@Inject lateinit var audioHandler: AudioHandler
@Inject lateinit var terminalController: TerminalController
@Inject lateinit var notificationHandler: NotificationHandler
@Inject lateinit var displayHandler: DisplayHandler
private lateinit var rootView: View
private lateinit var windowManager: WindowManager
private lateinit var adapter: AppListAdapter
private lateinit var orientationListener: OrientationEventListener
private var carUiController: CarUiController? = null
private var currentView = ViewType.VIEW_NONE
private var destroyed = false
private var initialMenuX = 0f
private var virtualDisplay: VirtualDisplay? = null
private var mediaProjection: MediaProjection? = null
private var projectionCode: Int = 0
private var projectionIntent: Intent? = null
private val apps = ArrayList<AppItem>()
private val screenLockReceiver = ScreenLockReceiver(this)
private val usbStateReceiver = UsbStateReceiver(this)
private val screenFilter = IntentFilter()
private val usbFilter = IntentFilter()
private var minitouchDaemon: MinitouchDaemon? = null
@Suppress("DEPRECATION")
private val wakeLock = (context.getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
SCREEN_DIM_WAKE_LOCK or ACQUIRE_CAUSES_WAKEUP, WAKELOCK_TAG)
/**
* Called when user has granted permission to start screen capture
*/
private val requestHandler = Handler(Handler.Callback { msg ->
if (msg?.what == Const.REQUEST_MEDIA_PROJECTION_PERMISSION) {
projectionCode = msg.arg2
projectionIntent = msg.obj as Intent
DevLog.d("Permission granted - starting screen capture")
startScreenCapture()
}
false
})
/**
* Initialize broadcast receivers
*/
init {
(context as App).component.inject(this)
screenFilter.addAction(Intent.ACTION_USER_PRESENT)
screenFilter.addAction(Intent.ACTION_SCREEN_ON)
screenFilter.addAction(Intent.ACTION_SCREEN_OFF)
usbFilter.addAction(Intent.ACTION_POWER_DISCONNECTED)
usbFilter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED)
usbFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED)
}
/**
* Called when Activity is created
*/
fun onCreate(rootView: View, windowManager: WindowManager, carUiController: CarUiController? = null) {
this.rootView = rootView
this.windowManager = windowManager
this.carUiController = carUiController
terminalController.init(rootView)
destroyed = false
Thread.setDefaultUncaughtExceptionHandler { _, e ->
e.printStackTrace()
DevLog.d("App has crashed: ${e.localizedMessage}")
onDestroy()
}
initViews()
initCarUiController()
requestProjectionPermission()
}
/**
* Called when Activity is resumed
*/
fun onResume() {
Shell.SU.available()
startMinitouch()
miniTouchHandler.updateValues()
loadApps()
DevLog.d("Car activity resumed")
}
/**
* Called when Activity is started
*/
fun onStart() {
DevLog.d("Car activity started")
onScreenOn()
notificationHandler.showNotification()
wakeLock.acquire(Long.MAX_VALUE)
audioHandler.start()
orientationListener.enable()
displayHandler.changeDisplaySettings()
brightnessHandler.setScreenBrightness()
rotationHandler.setScreenRotation()
context.registerReceiver(screenLockReceiver, screenFilter)
context.registerReceiver(usbStateReceiver, usbFilter)
}
/**
* Called when Activity is stopped
*/
fun onStop() {
DevLog.d("Car activity stopped")
if (wakeLock.isHeld) wakeLock.release()
audioHandler.stop()
orientationListener.disable()
stopScreenCapture()
}
/**
* Called when Activity is destroyed
*/
fun onDestroy() {
DevLog.d("Car activity destroyed")
destroyed = true
terminalController.stop()
stopMinitouch()
displayHandler.restoreDisplaySettings()
brightnessHandler.restoreScreenBrightness()
rotationHandler.restoreScreenRotation()
try {
context.unregisterReceiver(screenLockReceiver)
} catch (e: Exception) {
DevLog.d("Screen lock receiver already unregistered")
}
try {
context.unregisterReceiver(usbStateReceiver)
} catch (e: Exception) {
DevLog.d("USB state receiver already unregistered")
}
}
/**
* Called when Activity configuration has changed
*/
fun onConfigurationChanged() {
DevLog.d("Configuration changed")
miniTouchHandler.updateValues()
}
/**
* Called when car activity focus has changed
*/
fun onWindowFocusChanged() {
DevLog.d("On focus changed $destroyed")
if (!destroyed) {
updateScreenSize()
startScreenCapture()
}
}
/**
* Start mini touch daemon
*/
private fun startMinitouch() {
DevLog.d("Starting minitouch")
if (minitouchDaemon == null) {
minitouchDaemon = MinitouchDaemon(miniTouchHandler, this)
minitouchDaemon?.execute()
}
miniTouchHandler.init(rootView.car_surface_view, this)
}
/**
* Stop mini touch daemon
*/
private fun stopMinitouch() {
miniTouchHandler.clear()
miniTouchHandler.stop()
minitouchDaemon?.cancel(true)
}
/**
* Start dummy activity to handle permission granted result
*/
private fun startActivityForResult(what: Int, intent: Intent) {
ResultRequestActivity.startActivityForResult(context, requestHandler, what, intent, what)
}
/**
* Request permission to record screen
*/
private fun requestProjectionPermission() {
DevLog.d("Request projection permission")
val mediaProjectionManager = context.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
startActivityForResult(
Const.REQUEST_MEDIA_PROJECTION_PERMISSION,
mediaProjectionManager.createScreenCaptureIntent()
)
}
/**
* Initialize views and set listeners
*/
private fun initViews() {
DevLog.d("Initializing views")
orientationListener = object : OrientationEventListener(context) {
override fun onOrientationChanged(orientation: Int) {
miniTouchHandler.updateValues()
}
}
adapter = AppListAdapter(context, this)
rootView.car_app_grid.itemAnimator = null
rootView.car_app_grid.addItemDecoration(MarginDecoration(context))
rootView.car_app_grid.adapter = adapter
rootView.car_menu_app_list.setOnClickListener { showScreen(ViewType.VIEW_APP_LIST.value) }
rootView.car_menu_favorites.setOnClickListener { showScreen(ViewType.VIEW_FAVORITES.value) }
rootView.car_menu_terminal.setOnClickListener { showScreen(ViewType.VIEW_TERMINAL.value) }
rootView.car_menu_close.setOnClickListener { switchMenuVisibility(false) }
rootView.car_menu_back.setOnClickListener {
showScreen(ViewType.VIEW_NONE.value)
ShellExecutor("input keyevent ${KeyEvent.KEYCODE_BACK}").start()
}
rootView.car_menu_holder.post {
initialMenuX = rootView.car_menu_holder.width.toFloat()
rootView.car_menu_holder.x = -initialMenuX
}
rootView.car_menu_terminal.visibility = if (preferences.getBoolean(PreferenceHandler.KEY_DEBUG_DISABLED, true)) {
View.GONE
} else {
View.VISIBLE
}
switchMenuVisibility(preferences.getBoolean(PreferenceHandler.KEY_SIDEBAR_SWITCH, Const.DEFAULT_SHOW_SIDEBAR))
}
/**
* Initialize car UI controller
*/
private fun initCarUiController() {
carUiController?.statusBarController?.setTitle("")
carUiController?.statusBarController?.hideAppHeader()
carUiController?.statusBarController?.setAppBarAlpha(0.0f)
carUiController?.statusBarController?.setAppBarBackgroundColor(Color.WHITE)
carUiController?.statusBarController?.setDayNightStyle(DayNightStyle.AUTO)
carUiController?.menuController?.hideMenuButton()
}
/**
* Show/hide sidebar menu
*/
private fun switchMenuVisibility(visible: Boolean) {
rootView.car_menu_holder.post {
if (visible) {
rootView.car_menu_holder.animate().cancel()
rootView.car_menu_holder.animate()
.alpha(1f)
.x(0f)
.setStartDelay(Const.DEFAULT_ANIMATION_DELAY)
.setDuration(Const.DEFAULT_ANIMATION_DURATION)
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
super.onAnimationEnd(animation)
showScreen(preferences.getInt(PreferenceHandler.KEY_STARTUP_VALUE, Const.DEFAULT_SCREEN))
}
})
.start()
} else {
showScreen(ViewType.VIEW_NONE.value)
rootView.car_menu_holder.animate()
.alpha(0f)
.x(-initialMenuX)
.setStartDelay(Const.DEFAULT_ANIMATION_DELAY)
.setDuration(Const.DEFAULT_ANIMATION_DURATION)
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
super.onAnimationEnd(animation)
currentView = Companion.ViewType.VIEW_NONE
}
})
.start()
}
}
}
/**
* Show selected screen
*/
private fun showScreen(index: Int) {
if (index != currentView.value) {
DevLog.d("Showing screen: $index")
hideKeyboard()
when (index) {
ViewType.VIEW_NONE.value -> {
rootView.car_app_grid.visibility = View.GONE
rootView.view_car_terminal.visibility = View.GONE
rootView.car_app_favorite_empty.visibility = View.GONE
}
ViewType.VIEW_APP_LIST.value -> showAllApps()
ViewType.VIEW_FAVORITES.value -> showFavorites()
ViewType.VIEW_TERMINAL.value -> showTerminal()
}
}
}
/**
* Show terminal view
*/
private fun showTerminal() {
DevLog.d("Showing terminal")
currentView = ViewType.VIEW_TERMINAL
rootView.view_car_terminal.visibility = View.VISIBLE
rootView.car_app_grid.visibility = View.GONE
rootView.car_app_favorite_empty.visibility = View.GONE
rootView.car_app_grid_loader.visibility = View.GONE
}
/**
* Shows all device apps
*/
private fun showAllApps() {
DevLog.d("Showing all apps")
currentView = ViewType.VIEW_APP_LIST
rootView.car_app_grid.visibility = View.GONE
rootView.car_app_favorite_empty.visibility = View.GONE
rootView.view_car_terminal.visibility = View.GONE
if (apps.isEmpty()) {
rootView.car_app_grid_loader.visibility = View.VISIBLE
} else {
rootView.car_app_grid_loader.visibility = View.GONE
rootView.car_app_grid.visibility = View.VISIBLE
adapter.addAll(apps)
}
}
/**
* Shows all favorite apps if exists
*/
private fun showFavorites() {
DevLog.d("Showing favorite apps")
currentView = ViewType.VIEW_FAVORITES
rootView.car_app_grid.visibility = View.VISIBLE
rootView.view_car_terminal.visibility = View.GONE
showFavoritePlaceholder()
appHandler.getFavorites().let {
adapter.addAll(it)
}
}
/**
* Show / hide favorite app placeholder
*/
private fun showFavoritePlaceholder() {
rootView.car_app_grid_loader.visibility = View.GONE
appHandler.getFavorites().let {
if (it.isEmpty()) {
rootView.car_app_favorite_empty.visibility = View.VISIBLE
} else {
rootView.car_app_favorite_empty.visibility = View.GONE
}
}
}
/**
* @return selected app from app list or null
*/
private fun getSelectedApp(app: AppItem): AppItem? {
return apps.firstOrNull { it.equalTo(app) }
}
/**
* Query for installed device apps
*/
private fun loadApps() {
appHandler.loadApps(this)
}
/**
* Show error Toast with provided message String
*/
private fun showToastMessage(message: String) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
/**
* Hide keyboard
*/
private fun hideKeyboard() {
rootView.terminal_input.let {
it.postDelayed({
val inputManager = context.getSystemService(
Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputManager.hideSoftInputFromWindow(it.windowToken, 0)
}, 100)
}
}
/**
* Set current [AppItem] as favorite or not
*/
private fun setAppFavorite(app: AppItem) {
val wasFavorite = appHandler.isInFavorites(app)
apps.firstOrNull { it.equalTo(app) }?.favorite = !wasFavorite
preferences.putFavorites(apps.filter { it.favorite } as ArrayList<AppItem>)
if (ViewType.VIEW_FAVORITES == currentView) {
adapter.removeFavorite(app)
showFavoritePlaceholder()
} else {
adapter.setFavorite(app, wasFavorite)
}
if (wasFavorite) {
showToastMessage(context.getString(R.string.txt_removed_from_favorites))
} else {
showToastMessage(context.getString(R.string.txt_added_to_favorites))
}
}
/**
* Start screen capture
*/
private fun startScreenCapture() {
if (!destroyed) {
Handler(Looper.getMainLooper()).postDelayed({
rootView.car_surface_view.post {
DevLog.d("Will start screen capture if ($projectionCode) != 0 && ($projectionIntent) != null")
if (projectionIntent != null || projectionCode != 0) {
stopScreenCapture()
DevLog.d("Starting screen capture $projectionCode $projectionIntent")
miniTouchHandler.updateTouchTransformations(true)
val metrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(metrics)
val screenDensity = metrics.densityDpi
val mediaProjectionManager = context.getSystemService(
Context.MEDIA_PROJECTION_SERVICE
) as MediaProjectionManager
mediaProjection = mediaProjectionManager.getMediaProjection(projectionCode, projectionIntent!!)
mediaProjection?.let {
val width = rootView.car_surface_view.width
val height = rootView.car_surface_view.height
DevLog.d("Screen width: $width")
DevLog.d("Screen height: $height")
DevLog.d("Screen density: $screenDensity")
if (width > 0 && height > 0) {
virtualDisplay = it.createVirtualDisplay(
"ScreenCapture",
width, height, screenDensity,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
rootView.car_surface_view.holder.surface, null, null
)
}
}
}
}
}, Const.DEFAULT_ANIMATION_DELAY)
}
}
/**
* Stop screen capture
*/
private fun stopScreenCapture() {
DevLog.d("Stopping screen capture")
virtualDisplay?.release()
virtualDisplay = null
mediaProjection?.stop()
mediaProjection = null
}
/**
* Update screen size on focus change
*/
private fun updateScreenSize() {
if (preferences.getBoolean(PreferenceHandler.KEY_RESIZE_ENABLED, false)) {
val width = rootView.car_surface_view.width.toDouble()
val height = rootView.car_surface_view.height.toDouble()
if (width > 0 && height > 0) {
val ratio = width / height
val deviceWidth = miniTouchHandler.getDeviceDisplayWidth()
val deviceHeight = (deviceWidth * ratio).toInt()
if (deviceWidth > 0) {
displayHandler.updateScreenSize(deviceWidth, deviceHeight)
}
}
}
}
/**
* Called when device screen is unlocked
*/
override fun onScreenUnlocked() {
DevLog.d("Screen Unlocked")
rootView.car_surface_view.keepScreenOn = false
updateScreenSize()
}
/**
* Called when device screen is turned on
*/
override fun onScreenOn() {
DevLog.d("Screen ON")
rootView.car_surface_view.keepScreenOn = true
updateScreenSize()
startScreenCapture()
}
/**
* Called when device screen is turned off
*/
override fun onScreenOff() {
DevLog.d("Screen OFF")
rootView.car_surface_view.keepScreenOn = false
displayHandler.restoreDisplaySettings()
stopScreenCapture()
}
/**
* Called when screen is tapped with two fingers
*/
override fun onTapForMenu() {
DevLog.d("Show menu from tap")
switchMenuVisibility(true)
}
/**
* Called when device rotation has changed
*/
override fun onRotationChanged() {
DevLog.d("Rotation changed")
if (!destroyed) {
miniTouchHandler.updateValues()
onWindowFocusChanged()
}
}
/**
* Called when query for device apps is finished and results are returned
*/
override fun onAppListLoaded(apps: ArrayList<AppItem>) {
Handler(Looper.getMainLooper()).post {
DevLog.d("App list loaded")
var updated = false
if (this.apps.size == 0 || this.apps.size != apps.size) {
this.apps.addAll(apps)
updated = true
} else {
apps.forEach { newApp ->
var added = false
this.apps.forEach { currentApp ->
if (newApp.equalTo(currentApp)) {
added = true
}
}
if (!added) {
this.apps.add(newApp)
updated = true
}
}
}
// Update favorites
apps.forEach {
it.favorite = appHandler.isInFavorites(it)
}
if (updated) {
if (ViewType.VIEW_APP_LIST == currentView) {
showAllApps()
} else if (ViewType.VIEW_FAVORITES == currentView) {
showFavorites()
}
}
}
}
/**
* Called when query for device apps has failed
*/
override fun onAppListLoadFailed() {
DevLog.d("App list load failed")
if (ViewType.VIEW_APP_LIST == currentView) {
showToastMessage(context.getString(R.string.err_app_list_load_failed))
}
}
/**
* Called when an app in app list is clicked
*/
override fun onAppClicked(app: AppItem) {
Handler(Looper.getMainLooper()).post {
getSelectedApp(app)?.let {
DevLog.d("App clicked: $it")
context.packageManager.getLaunchIntentForPackage(it.packageName)?.let { intent ->
switchMenuVisibility(false)
context.startActivity(intent)
} ?: showToastMessage(context.getString(R.string.err_app_launch_failed))
}
}
}
/**
* Called when an app in app list is long clicked
*/
override fun onAppLongClicked(app: AppItem) {
Handler(Looper.getMainLooper()).post {
getSelectedApp(app)?.let {
DevLog.d("App long clicked: $it")
setAppFavorite(it)
}
}
}
/**
* Called when USB is disconnected
*/
override fun onUsbDisconnected() {
DevLog.d("Usb disconnected")
onDestroy()
}
/**
* Called when minitouch installed on path
*/
override fun onInstalled(path: String) {
DevLog.d("Initializing minitouch on $path")
ShellExecutor("chmod 777 $path").start()
ShellExecutor(path).start()
DevLog.d("Mini Touch started: $path")
miniTouchHandler.isInstalled = true
}
/**
* Called when minitouch has failed
*/
override fun onFailed() {
DevLog.d("Failed to install minitouch, trying again")
minitouchDaemon?.cancel(true)
minitouchDaemon = null
startMinitouch()
}
companion object {
const val WAKELOCK_TAG = "AAStream:WakeLock"
enum class ViewType(val value: Int) {
VIEW_NONE(0),
VIEW_APP_LIST(1),
VIEW_FAVORITES(2),
VIEW_TERMINAL(3)
}
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/activities/controllers/TerminalController.kt
================================================
package com.garage.aastream.activities.controllers
import android.os.Handler
import android.os.Looper
import android.view.View
import kotlinx.android.synthetic.main.view_car_terminal.view.*
import com.garage.aastream.interfaces.OnLogCallback
import com.garage.aastream.utils.DevLog
/**
* Created by Endy Rubbin on 28.05.2019 13:32.
* For project: AAStream
*/
class TerminalController : OnLogCallback {
private lateinit var rootView: View
/**
* Initialize the controller and add listener for logs
*/
fun init(rootView: View) {
this.rootView = rootView
DevLog.setCallback(this)
this.rootView.terminal_input_button.setOnClickListener {
this.rootView.terminal_input.text.toString().takeIf { it.isNotEmpty() }?.let {
DevLog.d("root@aa-stream:-$ $it")
this.rootView.terminal_input.setText("")
}
}
}
/**
* Remove log event callbacks
*/
fun stop() {
DevLog.removeCallback()
}
/**
* Called when logs are written
*/
override fun onLogWritten(log: String) {
Handler(Looper.getMainLooper()).post {
rootView.terminal_console.append(if (rootView.terminal_console.text.isEmpty()) "" else "\n")
rootView.terminal_console.append(log)
rootView.terminal_scroller.post { rootView.terminal_scroller.fullScroll(View.FOCUS_DOWN) }
}
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/adapters/AppListAdapter.kt
================================================
package com.garage.aastream.adapters
import android.content.Context
import android.graphics.drawable.Drawable
import android.net.Uri
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.OvershootInterpolator
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.garage.aastream.R
import com.garage.aastream.injection.GlideApp
import com.garage.aastream.interfaces.OnAppClickedCallback
import com.garage.aastream.models.AppItem
import com.garage.aastream.utils.DevLog
import kotlinx.android.synthetic.main.row_app_item.view.*
import kotlin.random.Random
/**
* Created by Endy Rubbin on 23.05.2019 15:02.
* For project: AAStream
*/
class AppListAdapter(
val context: Context,
val callback: OnAppClickedCallback
) : RecyclerView.Adapter<AppListAdapter.ViewHolder>() {
private var currentPosition = DEFAULT_INDEX
private val apps: ArrayList<AppItem> = ArrayList()
val glide = GlideApp.with(context)
/**
* Update the list adapter with new items
*/
fun addAll(apps: ArrayList<AppItem>) {
DevLog.d("Notifying app list ${apps.size} $apps")
this.apps.clear()
this.apps.addAll(apps)
currentPosition = DEFAULT_INDEX
notifyDataSetChanged()
}
/**
* Update list item and set it as favorite or not
*/
fun setFavorite(app: AppItem, favorite: Boolean) {
apps.indexOfFirst { it.equalTo(app)}.takeIf {it >= 0}?.let {
DevLog.d("Notifying app item $it $favorite ${apps[it]}")
apps[it].favorite = !favorite
currentPosition = DEFAULT_INDEX
notifyItemChanged(it)
}
}
/**
* Remove item from list
*/
fun removeFavorite(app: AppItem) {
apps.indexOfFirst { it.equalTo(app)}.takeIf {it >= 0}?.let {
apps.removeAt(it)
currentPosition = DEFAULT_INDEX
notifyItemRemoved(it)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.row_app_item, parent, false)
return ViewHolder(view)
}
override fun getItemCount(): Int {
return apps.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val animate = if (position > currentPosition) {
currentPosition = position
true
} else false
holder.bind(apps[position], animate)
}
override fun onViewDetachedFromWindow(holder: ViewHolder) {
super.onViewDetachedFromWindow(holder)
holder.clearAnimation()
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(app: AppItem, animate: Boolean = true) {
if (app.drawable != null) {
glide.load(app.drawable)
.placeholder(android.R.drawable.sym_def_app_icon)
.into(itemView.item_app_icon)
} else {
glide.load(if (app.icon != null) Uri.parse(app.icon) else android.R.drawable.sym_def_app_icon)
.placeholder(android.R.drawable.sym_def_app_icon)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
app.drawable = resource
return false
}
})
.into(itemView.item_app_icon)
}
itemView.item_app_name.text = app.label
itemView.item_app_favorite.visibility = if (app.favorite) View.VISIBLE else View.INVISIBLE
itemView.setOnClickListener { callback.onAppClicked(app) }
itemView.setOnLongClickListener {
callback.onAppLongClicked(app)
true
}
if (animate) {
val scale = Random.nextFloat() * (MAX_START_SCALE - MIN_START_SCALE) + MIN_START_SCALE
val delay = (Random.nextInt(MAX_START_DELAY) + MIN_START_DELAY).toLong()
val duration = (Random.nextInt(MAX_DURATION) + MIN_DURATION).toLong()
if (itemView.alpha != 0f) {
itemView.alpha = MIN_ALPHA
}
itemView.scaleX = scale
itemView.scaleY = scale
itemView.animate()
.alpha(1f)
.scaleX(1f)
.scaleY(1f)
.setStartDelay(delay)
.setDuration(duration)
.setInterpolator(OvershootInterpolator())
.start()
} else {
itemView.scaleX = 1f
itemView.scaleY = 1f
itemView.alpha = 1f
}
}
fun clearAnimation() {
itemView.animate().cancel()
itemView.clearAnimation()
}
}
companion object {
const val DEFAULT_INDEX = -1
const val MIN_ALPHA = 0.5f
const val MIN_START_SCALE = 0.4f
const val MAX_START_SCALE = 0.8f
const val MIN_START_DELAY = 50
const val MAX_START_DELAY = 200
const val MIN_DURATION = 200
const val MAX_DURATION = 500
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/handlers/AppHandler.kt
================================================
package com.garage.aastream.handlers
import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import com.garage.aastream.interfaces.OnAppListLoadedCallback
import com.garage.aastream.models.AppItem
import java.io.File
/**
* Created by Endy Rubbin on 23.05.2019 15:25.
* For project: AAStream
*/
class AppHandler(
val context: Context,
private val preferences: PreferenceHandler) {
private val packageManager: PackageManager = context.packageManager
/**
* Load all apps installed on device
*/
fun loadApps(callback: OnAppListLoadedCallback) {
Thread(Runnable {
packageManager.getInstalledApplications(0)?.let {
val apps = ArrayList<AppItem>()
it.forEach { info ->
packageManager.getLaunchIntentForPackage(info.packageName)?.let {
apps.add(getAppItem(info))
}
}
if (apps.isNotEmpty()) {
callback.onAppListLoaded(apps)
} else {
callback.onAppListLoadFailed()
}
}
}).start()
}
/**
* Gather app info
*
* @return the [AppItem] with set name and icon
*/
@Suppress("DEPRECATION")
private fun getAppItem(info: ApplicationInfo): AppItem {
val file = File(info.sourceDir)
val icon = if (info.icon != 0) "android.resource://" + info.packageName + "/" + info.icon else null
val name = if (!file.exists()) {
info.packageName
} else {
info.loadLabel(packageManager)
}.toString()
return AppItem(name, info.packageName, icon)
}
/**
* @return all favored [AppItem]s
*/
fun getFavorites(): ArrayList<AppItem> {
val favorites = preferences.getFavorites()
favorites.forEach { app -> app.favorite = false }
return favorites
}
/**
* Check if app is in favorites
*/
fun isInFavorites(app: AppItem): Boolean {
return getFavorites().firstOrNull { it.equalTo(app) }?.let { true } ?: false
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/handlers/AudioHandler.kt
================================================
package com.garage.aastream.handlers
import android.content.Context
import android.media.AudioManager.AUDIOFOCUS_GAIN
import android.support.car.Car
import android.support.car.CarConnectionCallback
import android.support.car.media.CarAudioManager
import android.support.car.media.CarAudioManager.CAR_AUDIO_USAGE_DEFAULT
import com.garage.aastream.utils.DevLog
/**
* Created by Endy Rubbin on 22.05.2019 14:31.
* For project: AAStream
*/
class AudioHandler(
context: Context,
private val preferences: PreferenceHandler
) {
private var car: Car? = null
init {
car = Car.createCar(context, object : CarConnectionCallback() {
override fun onConnected(car: Car) {
requestAudioFocus(car)
}
override fun onDisconnected(car: Car) {
abandonAudioFocus(car)
}
})
}
/**
* Start audio handler
*/
fun start() {
car?.connect()
}
/**
* Stop audio handler
*/
fun stop() {
car?.disconnect()
}
/**
* Request car audio focus
*/
private fun requestAudioFocus(car: Car) {
if (!preferences.getBoolean(PreferenceHandler.KEY_AUDIO_FOCUS, false)) {
return
}
DevLog.d("RequestAudioFocus")
try {
val carAM = car.getCarManager(CarAudioManager::class.java)
carAM.requestAudioFocus(
null,
carAM.getAudioAttributesForCarUsage(CAR_AUDIO_USAGE_DEFAULT),
AUDIOFOCUS_GAIN,
0
)
} catch (e: Exception) {
DevLog.d("RequestAudioFocus exception: $e")
}
}
/**
* Abandon car audio focus
*/
private fun abandonAudioFocus(car: Car) {
DevLog.d("AbandonAudioFocus")
try {
val carAM = car.getCarManager(CarAudioManager::class.java)
carAM.abandonAudioFocus(null, carAM.getAudioAttributesForCarUsage(CAR_AUDIO_USAGE_DEFAULT))
} catch (e: Exception) {
DevLog.d("AbandonAudioFocus exception: $e")
}
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/handlers/BrightnessHandler.kt
================================================
package com.garage.aastream.handlers
import android.content.Context
import android.os.Build
import android.provider.Settings
import android.provider.Settings.System.SCREEN_BRIGHTNESS
import com.garage.aastream.utils.DevLog
/**
* Created by Endy Rubbin on 26.05.2019 19:41.
* For project: AAStream
*/
class BrightnessHandler(val context: Context, val preferences: PreferenceHandler) {
private val systemBrightness: Int
init {
val savedSystemBrightness = preferences.getInt(PreferenceHandler.KEY_SYSTEM_BRIGHTNESS, -1)
val currentSystemBrightness = (Settings.System.getInt(context.contentResolver,
SCREEN_BRIGHTNESS).toFloat() / 255 * 100).toInt()
systemBrightness = if (savedSystemBrightness == -1) {
preferences.putInt(PreferenceHandler.KEY_SYSTEM_BRIGHTNESS, currentSystemBrightness)
currentSystemBrightness
} else {
savedSystemBrightness
}
}
/**
* @return true if brightness can be changed
*/
private fun canChangeBrightness(): Boolean {
return preferences.getBoolean(PreferenceHandler.KEY_BRIGHTNESS_SWITCH, false) &&
(Build.VERSION.SDK_INT < 23 || Settings.System.canWrite(context))
}
/**
* @return current / saved screen brightness
*/
fun getScreenBrightness(): Int {
return if (canChangeBrightness()) {
preferences.getInt(PreferenceHandler.KEY_BRIGHTNESS_VALUE, systemBrightness)
} else {
systemBrightness
}
}
/**
* Update device screen brightness
*/
fun setScreenBrightness(value: Int = getScreenBrightness()) {
if (canChangeBrightness()) {
DevLog.d("Changing screen brightness: $value $systemBrightness")
Settings.System.putInt(context.contentResolver, SCREEN_BRIGHTNESS, 255 * value / 100)
}
}
/**
* Restore previous screen brightness
*/
fun restoreScreenBrightness() {
DevLog.d("Restoring screen brightness $systemBrightness")
setScreenBrightness(systemBrightness)
preferences.putInt(PreferenceHandler.KEY_SYSTEM_BRIGHTNESS, -1)
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/handlers/DisplayHandler.kt
================================================
package com.garage.aastream.handlers
import android.content.Context
import com.garage.aastream.shell.ShellExecutor
import com.garage.aastream.utils.DevLog
/**
* Created by Endy Rubbin on 02.07.2019 10:21.
* For project: AAStream
*/
class DisplayHandler(val context: Context, val preferences: PreferenceHandler) {
/**
* Change initial display settings
*/
fun changeDisplaySettings() {
DevLog.d("Changing display settings")
if (preferences.getBoolean(PreferenceHandler.KEY_IMMERSIVE_MODE, false)) {
ShellExecutor("settings put global policy_control immersive.full=*").start()
}
}
/**
* Update screen size
*/
fun updateScreenSize(deviceWidth: Int, deviceHeight: Int) {
DevLog.d("Setting screen size: $deviceWidth $deviceHeight")
ShellExecutor("wm size " + deviceWidth + "x" + deviceHeight).start()
}
/**
* Restore display settings
*/
fun restoreDisplaySettings() {
DevLog.d("Restoring display settings")
if (preferences.getBoolean(PreferenceHandler.KEY_IMMERSIVE_MODE, false)) {
ShellExecutor("settings put global policy_control none*").start()
}
ShellExecutor("wm size reset").start()
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/handlers/NotificationHandler.kt
================================================
package com.garage.aastream.handlers
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Build
import com.garage.aastream.R
import com.garage.aastream.activities.KillerActivity
/**
* Created by Endy Rubbin on 07.06.2019 11:29.
* For project: AAStream
*/
class NotificationHandler(val context: Context) {
private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
/**
* Show notification when car activity is started to safely exit the app and restore previous state
*/
@Suppress("DEPRECATION")
fun showNotification() {
createChannel(context)
val builder: Notification.Builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder(context, CHANNEL_ID)
} else {
Notification.Builder(context)
}
val title = context.getString(R.string.app_name)
val message = context.getString(R.string.txt_app_running_notification)
val notifyIntent = Intent(context, KillerActivity::class.java)
notifyIntent.putExtra(ACTION_EXIT, true)
notifyIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK
notifyIntent.flags = Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
val pendingIntent = PendingIntent.getActivity(context, 0,
notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT)
val notification = builder
.setSmallIcon(R.drawable.ic_small_icon)
.setOnlyAlertOnce(true)
.setPriority(Notification.PRIORITY_MAX)
.setContentTitle(title)
.setWhen(0)
.setShowWhen(true)
.setStyle(Notification.BigTextStyle().bigText(message))
.setContentText(message)
.addAction(Notification
.Action(0, context.getString(R.string.txt_notification_exit), pendingIntent))
.build()
notificationManager.notify(NOTIFICATION_ID, notification)
}
/**
* Clear ingoing notification when app is destroyed
*/
fun clearNotification() {
notificationManager.cancel(NOTIFICATION_ID)
}
/**
* Create notification channel for android O and up
*/
private fun createChannel(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val importance = NotificationManager.IMPORTANCE_HIGH
val notificationChannel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME, importance)
notificationChannel.enableVibration(true)
notificationChannel.setShowBadge(true)
notificationChannel.enableLights(true)
notificationChannel.lightColor = Color.parseColor("#e8334a")
notificationChannel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
notificationManager.createNotificationChannel(notificationChannel)
}
}
companion object {
const val NOTIFICATION_ID = 1337
const val CHANNEL_ID = "AAStream Channel ID"
const val CHANNEL_NAME = "AAStream Notification Name"
const val ACTION_EXIT = "Action Exit"
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/handlers/PreferenceHandler.kt
================================================
package com.garage.aastream.handlers
import android.app.Application
import android.content.Context
import com.garage.aastream.models.AppItem
import com.garage.aastream.models.AppItemWrapper
import com.garage.aastream.utils.Const
import com.google.gson.Gson
import java.util.*
/**
* Created by Endy Rubbin on 22.05.2019 14:31.
* For project: AAStream
*/
class PreferenceHandler(context: Application) {
private val preferences = context.getSharedPreferences(Const.PREFERENCES_NAME, Context.MODE_PRIVATE)
private val gson: Gson = Gson()
/**
* Save a boolean value preferences
*/
fun putBoolean(key: String, value: Boolean) {
preferences.edit().putBoolean(key, value).apply()
}
/**
* Load a boolean value from preferences
*/
fun getBoolean(key: String, value: Boolean): Boolean {
return preferences.getBoolean(key, value)
}
/**
* Save a int value preferences
*/
fun putInt(key: String, value: Int) {
preferences.edit().putInt(key, value).apply()
}
/**
* Load a int value from preferences
*/
fun getInt(key: String, value: Int): Int {
return preferences.getInt(key, value)
}
/**
* Save a list of [AppItem]s in preferences
*/
fun putFavorites(apps: ArrayList<AppItem>) {
val data = gson.toJson(AppItemWrapper(apps))
preferences.edit().putString(KEY_FAVORITE_APPS, data).apply()
}
/**
* Load a list of [AppItem]s from preferences
*/
fun getFavorites(): ArrayList<AppItem> {
val data = preferences.getString(KEY_FAVORITE_APPS, null)
return if (data != null) {
gson.fromJson(data, AppItemWrapper::class.java).apps
} else ArrayList()
}
companion object {
const val KEY_AUDIO_FOCUS = "audio_focus"
const val KEY_FAVORITE_APPS = "favorite_app_list"
const val KEY_ROTATION_SWITCH = "rotation_switch"
const val KEY_ROTATION_VALUE = "rotation_value"
const val KEY_BRIGHTNESS_SWITCH = "brightness_switch"
const val KEY_BRIGHTNESS_VALUE = "brightness_value"
const val KEY_SYSTEM_BRIGHTNESS = "system_brightness"
const val KEY_SIDEBAR_SWITCH = "sidebar_switch"
const val KEY_STARTUP_VALUE = "sidebar_value"
const val KEY_DEBUG_DISABLED = "debug_enabled"
const val KEY_OPEN_MENU_METHOD = "menu_open_method"
const val KEY_RESIZE_ENABLED = "resize_enabled"
const val KEY_IMMERSIVE_MODE = "immersive_mode"
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/handlers/RotationHandler.kt
================================================
package com.garage.aastream.handlers
import android.content.Context
import android.os.Build
import android.provider.Settings
import android.provider.Settings.System.ACCELEROMETER_ROTATION
import android.provider.Settings.System.USER_ROTATION
import android.view.Surface
import com.garage.aastream.utils.DevLog
/**
* Created by Endy Rubbin on 26.05.2019 20:27.
* For project: AAStream
*/
class RotationHandler(val context: Context, val preferences: PreferenceHandler) {
private val systemAutoRotation = Settings.System.getInt(context.contentResolver, ACCELEROMETER_ROTATION)
/**
* @return true if rotation can be changed
*/
private fun canChangeRotation(): Boolean {
return preferences.getBoolean(PreferenceHandler.KEY_ROTATION_SWITCH, false) &&
(Build.VERSION.SDK_INT < 23 || Settings.System.canWrite(context))
}
/**
* @return current / saved screen brightness
*/
fun getScreenRotation(): Int {
return if (canChangeRotation()) {
preferences.getInt(PreferenceHandler.KEY_ROTATION_VALUE, 0)
} else {
0
}
}
/**
* Update device screen brightness
*/
fun setScreenRotation(value: Int = getScreenRotation(), autoRotation: Int = 0) {
if (canChangeRotation()) {
DevLog.d("Changing screen rotation: $value $systemAutoRotation")
Settings.System.putInt(context.contentResolver, ACCELEROMETER_ROTATION, autoRotation)
Settings.System.putInt(context.contentResolver, USER_ROTATION, getOrientation(value))
}
}
/**
* @return System rotation value for selected option
*/
private fun getOrientation(value: Int): Int {
return when(value) {
0 -> Surface.ROTATION_0
1 -> Surface.ROTATION_90
2 -> Surface.ROTATION_180
3 -> Surface.ROTATION_270
else -> Surface.ROTATION_0
}
}
/**
* Restore previous screen brightness
*/
fun restoreScreenRotation() {
DevLog.d("Restoring screen rotation")
setScreenRotation(Surface.ROTATION_0, systemAutoRotation)
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/injection/GlideModule.kt
================================================
package com.garage.aastream.injection
import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.module.AppGlideModule
/**
* Created by Endy Rubbin on 24.05.2019 17:48.
* For project: AAStream
*/
@GlideModule
class GlideModule : AppGlideModule()
================================================
FILE: app/src/main/java/com/garage/aastream/injection/InjectionComponent.kt
================================================
package com.garage.aastream.injection
import com.garage.aastream.App
import com.garage.aastream.activities.CarDebugActivity
import com.garage.aastream.activities.CarMainActivity
import com.garage.aastream.activities.KillerActivity
import com.garage.aastream.activities.SettingsActivity
import com.garage.aastream.activities.controllers.CarActivityController
import com.garage.aastream.handlers.PreferenceHandler
import dagger.Component
import javax.inject.Singleton
/**
* Created by Endy Rubbin on 22.05.2019 14:48.
* For project: AAStream
*/
@Singleton
@Component(modules = [InjectionModule::class])
interface InjectionComponent {
fun inject(target: App)
fun inject(target: SettingsActivity)
fun inject(target: CarMainActivity)
fun inject(target: CarDebugActivity)
fun inject(target: KillerActivity)
fun inject(target: CarActivityController)
fun exposePreferenceHandler(): PreferenceHandler
}
================================================
FILE: app/src/main/java/com/garage/aastream/injection/InjectionModule.kt
================================================
package com.garage.aastream.injection
import android.app.Application
import com.garage.aastream.activities.controllers.CarActivityController
import com.garage.aastream.activities.controllers.TerminalController
import com.garage.aastream.handlers.*
import com.garage.aastream.minitouch.MiniTouchHandler
import com.garage.aastream.utils.PhenotypePatcher
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
/**
* Created by Endy Rubbin on 22.05.2019 14:44.
* For project: AAStream
*/
@Module
class InjectionModule(private val context: Application) {
@Singleton
@Provides
fun provideAudioHandler(preferences: PreferenceHandler): AudioHandler {
return AudioHandler(context, preferences)
}
@Singleton
@Provides
fun provideMiniTouchHandler(preferences: PreferenceHandler): MiniTouchHandler {
return MiniTouchHandler(context, preferences)
}
@Singleton
@Provides
fun providePreferenceHandler(): PreferenceHandler {
return PreferenceHandler(context)
}
@Singleton
@Provides
fun provideAppHandler(preferences: PreferenceHandler): AppHandler {
return AppHandler(context, preferences)
}
@Singleton
@Provides
fun provideBrightnessHandler(preferences: PreferenceHandler): BrightnessHandler {
return BrightnessHandler(context, preferences)
}
@Singleton
@Provides
fun provideRotationHandler(preferences: PreferenceHandler): RotationHandler {
return RotationHandler(context, preferences)
}
@Singleton
@Provides
fun providePhenotypePatcher(): PhenotypePatcher {
return PhenotypePatcher(context)
}
@Singleton
@Provides
fun provideCarActivityController(): CarActivityController {
return CarActivityController(context)
}
@Singleton
@Provides
fun provideTerminalController(): TerminalController {
return TerminalController()
}
@Singleton
@Provides
fun provideNotificationHandler(): NotificationHandler {
return NotificationHandler(context)
}
@Singleton
@Provides
fun provideDisplayHandler(preferences: PreferenceHandler): DisplayHandler {
return DisplayHandler(context, preferences)
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/interfaces/OnAppClickedCallback.kt
================================================
package com.garage.aastream.interfaces
import com.garage.aastream.models.AppItem
/**
* Created by Endy Rubbin on 23.05.2019 15:05.
* For project: AAStream
*/
interface OnAppClickedCallback {
/**
* Called when [AppItem] is selected at adapter position
*/
fun onAppClicked(app: AppItem)
/**
* Called when [AppItem] is long clicked ar adapter position to add to favorites
*/
fun onAppLongClicked(app: AppItem)
}
================================================
FILE: app/src/main/java/com/garage/aastream/interfaces/OnAppListLoadedCallback.kt
================================================
package com.garage.aastream.interfaces
import com.garage.aastream.models.AppItem
/**
* Created by Endy Rubbin on 23.05.2019 15:26.
* For project: AAStream
*/
interface OnAppListLoadedCallback {
/**
* Called when device app list has finished loading
*/
fun onAppListLoaded(apps: ArrayList<AppItem>)
/**
* Called when device app list failed to load
*/
fun onAppListLoadFailed()
}
================================================
FILE: app/src/main/java/com/garage/aastream/interfaces/OnLogCallback.kt
================================================
package com.garage.aastream.interfaces
/**
* Created by Endy Rubbin on 28.05.2019 13:35.
* For project: AAStream
*/
interface OnLogCallback {
/**
* Called when a log line is written to console
*/
fun onLogWritten(log: String)
}
================================================
FILE: app/src/main/java/com/garage/aastream/interfaces/OnMenuTapCallback.kt
================================================
package com.garage.aastream.interfaces
/**
* Created by Endy Rubbin on 27.05.2019 13:35.
* For project: AAStream
*/
interface OnMenuTapCallback {
/**
* Called when tap to open menu is detected
*/
fun onTapForMenu()
}
================================================
FILE: app/src/main/java/com/garage/aastream/interfaces/OnMinitouchCallback.kt
================================================
package com.garage.aastream.interfaces
/**
* Created by Endy Rubbin on 10.06.2019 14:20.
* For project: AAStream
*/
interface OnMinitouchCallback {
/**
* Called when minitouch is installed
*/
fun onInstalled(path: String)
/**
* Called when minitouch has failed
*/
fun onFailed()
}
================================================
FILE: app/src/main/java/com/garage/aastream/interfaces/OnPatchStatusCallback.kt
================================================
package com.garage.aastream.interfaces
/**
* Created by Endy Rubbin on 27.05.2019 10:33.
* For project: AAStream
*/
interface OnPatchStatusCallback {
/**
* Called if patch was successful
*/
fun onPatchSuccessful()
/**
* Called if patch has failed
*/
fun onPatchFailed()
}
================================================
FILE: app/src/main/java/com/garage/aastream/interfaces/OnRotationChangedCallback.kt
================================================
package com.garage.aastream.interfaces
/**
* Created by Endy Rubbin on 03.06.2019 16:38.
* For project: AAStream
*/
interface OnRotationChangedCallback {
/**
* Called when device rotation has changed
*/
fun onRotationChanged()
}
================================================
FILE: app/src/main/java/com/garage/aastream/interfaces/OnScreenLockCallback.kt
================================================
package com.garage.aastream.interfaces
/**
* Created by Endy Rubbin on 22.05.2019 13:06.
* For project: AAStream
*/
interface OnScreenLockCallback {
/**
* Called when device lock screen is unlocked
*/
fun onScreenUnlocked()
/**
* Called when device screen is turned on and available for recording
*/
fun onScreenOn()
/**
* Called when device screen is turned off / sleeping
*/
fun onScreenOff()
}
================================================
FILE: app/src/main/java/com/garage/aastream/minitouch/MiniTouchHandler.kt
================================================
package com.garage.aastream.minitouch
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Point
import android.view.MotionEvent
import android.view.MotionEvent.*
import android.view.Surface.*
import android.view.SurfaceView
import android.view.View
import android.view.WindowManager
import com.garage.aastream.handlers.PreferenceHandler
import com.garage.aastream.interfaces.OnMenuTapCallback
import com.garage.aastream.interfaces.OnMinitouchCallback
import com.garage.aastream.utils.DevLog
import com.garage.aastream.views.FingerTapDetector
import eu.chainfire.libsuperuser.Shell
import java.io.File
import java.io.FileOutputStream
/**
* Created by Endy Rubbin on 22.05.2019 13:25.
* For project: AAStream
*/
class MiniTouchHandler(
private val context: Context,
private val preferenceHandler: PreferenceHandler
): View.OnTouchListener {
private var fingerTapDetector: FingerTapDetector? = null
private val miniTouchSocket: MiniTouchSocket = MiniTouchSocket()
private var surfaceView: SurfaceView? = null
private var callback: OnMinitouchCallback? = null
private var deviceScreenSize = Point()
private var deviceDisplaySize = Point()
private var deviceScreenRotation: Int = ROTATION_0
private var screenRotation: Int = ROTATION_0
private var screenWidth = 0.0
private var screenHeight = 0.0
private var projectionOffsetX = 0.0
private var projectionOffsetY = 0.0
private var projectionWidth = 0.0
private var projectionHeight = 0.0
var isInstalled = false
fun getDeviceDisplayWidth(): Int {
return deviceDisplaySize.x
}
/**
* Reads and updates current screen values
*/
fun updateValues() {
val windowManager = context.applicationContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager
windowManager.defaultDisplay.getRealSize(deviceScreenSize)
deviceScreenRotation = windowManager.defaultDisplay.rotation
DevLog.d("Updating values: $deviceScreenRotation")
if (deviceScreenRotation == ROTATION_0 || deviceScreenRotation == ROTATION_180) {
deviceDisplaySize.x = deviceScreenSize.x
deviceDisplaySize.y = deviceScreenSize.y
} else {
deviceDisplaySize.x = deviceScreenSize.y
deviceDisplaySize.y = deviceScreenSize.x
}
updateTouchTransformations(true)
}
@SuppressLint("ClickableViewAccessibility")
fun init(surfaceView: SurfaceView, callback: OnMenuTapCallback) {
this.surfaceView = surfaceView
this.surfaceView?.setOnTouchListener(this)
fingerTapDetector = FingerTapDetector(context, preferenceHandler, callback)
}
/**
* Clear views and callbacks
*/
fun clear() {
surfaceView = null
fingerTapDetector?.removeCallback()
fingerTapDetector = null
}
/**
* Start mini touch handler
*/
fun start(callback: OnMinitouchCallback) {
this.callback = callback
val path = install()
DevLog.d("Mini Touch path: $path")
if (path?.isNotEmpty() == true) {
callback.onInstalled(path)
}
}
/**
* Stop mini touch handler
*/
fun stop() {
this.callback = null
val pid = miniTouchSocket.getPid()
miniTouchSocket.disconnect()
DevLog.d("Mini Touch stopped: $pid")
if (pid != 0) {
Shell.Pool.SU.run("kill $pid")
}
}
/**
* Install minitouch library
*/
private fun install(): String? {
val path = context.filesDir.absolutePath
val target = File("$path/", "minitouch")
val folder = File(path)
try {
val abi = detectAbi()
val assetName = "libs/$abi/minitouch"
DevLog.d("Minitouch already exists: ${folder.exists()}")
if (!folder.exists()) folder.mkdirs()
DevLog.d("Installing minitouch $assetName")
context.resources.assets.open(assetName).use { input ->
DevLog.d("Asset opened, writing to file: ${target.absolutePath}")
if (!target.exists()) target.createNewFile()
FileOutputStream(target.absolutePath).use { output ->
DevLog.d("Copying asset to file ${target.absolutePath}")
input.copyTo(output)
input.close()
output.flush()
output.close()
DevLog.d("Mini Touch installed ${target.absolutePath}")
return target.absolutePath
}
}
} catch (e: Exception) {
return if (target.exists()) {
DevLog.d("Mini Touch installed ${target.absolutePath}")
target.absolutePath
} else {
e.printStackTrace()
DevLog.d("Failed to install Mini Touch: $e")
null
}
}
}
/**
* Detect device ABI
*/
private fun detectAbi(): String {
var abi: String? = null
Shell.Pool.SH.run("getprop ro.product.cpu.abi", object : Shell.OnSyncCommandLineListener {
override fun onSTDERR(line: String?) {
DevLog.d("Failed to detect Abi: $line")
}
override fun onSTDOUT(line: String?) {
DevLog.d("Shell line read: $line")
if (abi == null) abi = line
}
})
DevLog.d("Detected Abi: $abi")
return if (abi != null && abi!!.isNotEmpty()) abi!! else "armeabi"
}
/**
* Handle device screen touch events
*/
@SuppressLint("ClickableViewAccessibility")
override fun onTouch(v: View?, event: MotionEvent): Boolean {
if (!miniTouchSocket.isConnected()) {
if (!isInstalled) {
DevLog.d("Minitouch not installed")
callback?.onFailed()
return false
}
miniTouchSocket.connect(callback)
updateTouchTransformations(true)
} else {
updateTouchTransformations(false)
}
var isConnected = miniTouchSocket.isConnected()
val action = event.actionMasked
var i = 0
while (i < event.pointerCount && isConnected) {
val id = event.getPointerId(i)
val x = (event.getX(i) - projectionOffsetX) / projectionWidth
val y = (event.getY(i) - projectionOffsetY) / projectionHeight
val pressure = event.getPressure(i).toDouble()
var rx = x
var ry = y
when (screenRotation) {
ROTATION_0 -> {
rx = x
ry = y
}
ROTATION_90 -> {
rx = 1.0 - y
ry = x
}
ROTATION_180 -> {
rx = 1.0 - x
ry = 1.0 - y
}
ROTATION_270 -> {
rx = y
ry = 1.0 - x
}
}
when (action) {
ACTION_DOWN, ACTION_POINTER_DOWN -> isConnected = isConnected && miniTouchSocket.touchDown(id, rx, ry, pressure)
ACTION_MOVE -> isConnected = isConnected && miniTouchSocket.touchMove(id, rx, ry, pressure)
ACTION_UP, ACTION_CANCEL -> isConnected = isConnected && miniTouchSocket.touchUpAll()
ACTION_POINTER_UP -> isConnected = isConnected && miniTouchSocket.touchUp(id)
}
i++
}
if (isConnected) {
miniTouchSocket.touchCommit()
}
fingerTapDetector?.onTouchEvent(event)
return true
}
/**
* Update touch coordinate transformations
*/
fun updateTouchTransformations(force: Boolean) {
if (surfaceView == null ||
deviceScreenRotation == screenRotation &&
deviceScreenSize.equals(screenWidth.toInt(), screenHeight.toInt())
&& !force) {
return
}
screenRotation = deviceScreenRotation
screenWidth = deviceScreenSize.x.toDouble()
screenHeight = deviceScreenSize.y.toDouble()
val surfaceWidth = surfaceView?.width?.toDouble() ?: 0.0
val surfaceHeight = surfaceView?.height?.toDouble() ?: 0.0
val factX = surfaceWidth / screenWidth
val factY = surfaceHeight / screenHeight
val fact = if (factX < factY) factX else factY
projectionWidth = fact * screenWidth
projectionHeight = fact * screenHeight
projectionOffsetX = (surfaceWidth - projectionWidth) / 2.0
projectionOffsetY = (surfaceHeight - projectionHeight) / 2.0
if (screenRotation == ROTATION_0 || screenRotation == ROTATION_180) {
miniTouchSocket.updateTouchTransformations(this.screenWidth, this.screenHeight, deviceDisplaySize)
} else {
miniTouchSocket.updateTouchTransformations(this.screenHeight, this.screenWidth, deviceDisplaySize)
}
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/minitouch/MiniTouchSocket.kt
================================================
package com.garage.aastream.minitouch
import android.graphics.Point
import android.net.LocalSocket
import android.net.LocalSocketAddress
import com.garage.aastream.interfaces.OnMinitouchCallback
import com.garage.aastream.utils.DevLog
import java.io.InputStream
import java.io.OutputStream
/**
* Created by Endy Rubbin on 22.05.2019 13:25.
* For project: AAStream
*/
class MiniTouchSocket {
private var socketLocal: LocalSocket? = null
private var outputStream: OutputStream? = null
private var version: Int = 0
private var maxContact: Int = 0
private var maxX: Double = 0.toDouble()
private var maxY: Double = 0.toDouble()
private var maxPressure: Double = 0.toDouble()
private var pid: Int = 0
private var projectionOffsetX: Double = 0.toDouble()
private var projectionOffsetY: Double = 0.toDouble()
private var projectionWidth: Double = 0.toDouble()
private var projectionHeight: Double = 0.toDouble()
private var touchXScale: Double = 0.toDouble()
private var touchYScale: Double = 0.toDouble()
internal fun disconnect() {
DevLog.d("Mini Touch disconnect")
if (isConnected()) {
try {
socketLocal!!.close()
} catch (e: Exception) {
DevLog.d("Failed to disconnect socket: $e")
}
outputStream = null
socketLocal = null
}
}
internal fun connect(callback: OnMinitouchCallback?): Boolean {
DevLog.d("Mini Touch connect socket")
disconnect()
val socket = LocalSocket()
try {
socket.connect(LocalSocketAddress(DEFAULT_SOCKET_NAME))
if (inputReadParams(socket.inputStream)) {
outputStream = socket.outputStream
socketLocal = socket
} else {
socket.close()
}
} catch (e: Exception) {
DevLog.d("Failed to connect socket: $e")
socketLocal = null
callback?.onFailed()
}
return isConnected()
}
fun isConnected(): Boolean {
return socketLocal != null
}
internal fun getPid(): Int {
DevLog.d("Mini Touch pid: $pid")
return pid
}
private fun inputReadParams(stream: InputStream): Boolean {
DevLog.d("Mini Touch read")
val buffer = ByteArray(128)
pid = 0
try {
if (stream.read(buffer) == -1) {
DevLog.d("Mini Touch read error")
return false
}
} catch (e: Exception) {
DevLog.d("Mini Touch read exception: $e")
return false
}
val dataLine = String(buffer)
val lines = dataLine.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (lines.size < 3) {
DevLog.d("Error: less then 3 lines")
return false
}
val versionLine = lines[0].split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (versionLine.size == 2) {
version = Integer.parseInt(versionLine[1])
}
val limitsLine = lines[1].split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (limitsLine.size == 5) {
maxContact = Integer.parseInt(limitsLine[1])
maxX = Integer.parseInt(limitsLine[2]).toDouble()
maxY = Integer.parseInt(limitsLine[3]).toDouble()
maxPressure = Integer.parseInt(limitsLine[4]).toDouble()
}
val pidLine = lines[2].split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (pidLine.size == 2) {
pid = Integer.parseInt(pidLine[1])
}
DevLog.d("Read pid $pid")
return true
}
private fun writeOutput(command: String): Boolean {
if (outputStream == null)
return false
try {
outputStream!!.write(command.toByteArray())
} catch (e: Exception) {
return false
}
return true
}
private fun validateBounds(x: Double, y: Double): Boolean {
return x >= 0.0 && x < maxX && y >= 0.0 && y < maxY
}
internal fun updateTouchTransformations(screenWidth: Double, screenHeight: Double, displaySize: Point) {
val displayWidth = displaySize.x.toDouble()
val displayHeight = displaySize.y.toDouble()
val factX = displayWidth / screenWidth
val factY = displayHeight / screenHeight
val fact = if (factX < factY) factX else factY
projectionWidth = fact * screenWidth
projectionHeight = fact * screenHeight
projectionOffsetX = (displayWidth - projectionWidth) / 2.0
projectionOffsetY = (displayHeight - projectionHeight) / 2.0
touchXScale = maxX / displayWidth
touchYScale = maxY / displayHeight
}
internal fun touchDown(id: Int, x: Double, y: Double, pressure: Double): Boolean {
val touchX = (projectionOffsetX + x * projectionWidth) * touchXScale
val touchY = (projectionOffsetY + y * projectionHeight) * touchYScale
val touchPressure = pressure * maxPressure
if (!validateBounds(touchX, touchY))
return true
return writeOutput(String.format("d %d %d %d %d\n", id, touchX.toInt(), touchY.toInt(), touchPressure.toInt()))
}
internal fun touchMove(id: Int, x: Double, y: Double, pressure: Double): Boolean {
val touchX = (projectionOffsetX + x * projectionWidth) * touchXScale
val touchY = (projectionOffsetY + y * projectionHeight) * touchYScale
val touchPressure = pressure * maxPressure
if (!validateBounds(touchX, touchY))
return true
return writeOutput(String.format("m %d %d %d %d\n", id, touchX.toInt(), touchY.toInt(), touchPressure.toInt()))
}
internal fun touchUp(id: Int): Boolean {
return writeOutput(String.format("u %d\n", id))
}
internal fun touchUpAll(): Boolean {
var ok = true
for (i in 0 until maxContact)
ok = ok && touchUp(i)
return ok
}
internal fun touchCommit() {
writeOutput("c\n")
}
companion object {
private const val DEFAULT_SOCKET_NAME = "minitouch"
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/minitouch/MinitouchDaemon.kt
================================================
package com.garage.aastream.minitouch
import android.os.AsyncTask
import com.garage.aastream.interfaces.OnMinitouchCallback
import com.garage.aastream.utils.DevLog
/**
* Created by Endy Rubbin on 10.06.2019 14:27.
* For project: AAStream
*/
class MinitouchDaemon(
private val miniTouchHandler: MiniTouchHandler,
val callback: OnMinitouchCallback
) : AsyncTask<Void, Void, Void>() {
override fun doInBackground(vararg voids: Void): Void? {
DevLog.d("Minitouch daemon started")
miniTouchHandler.start(callback)
return null
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/models/AppItem.kt
================================================
package com.garage.aastream.models
import android.graphics.drawable.Drawable
/**
* Created by Endy Rubbin on 23.05.2019 15:03.
* For project: AAStream
*/
data class AppItem(val label: String, val packageName: String, val icon: String?, var favorite: Boolean = false) {
@Transient var drawable: Drawable? = null
fun equalTo(app: AppItem): Boolean {
return this.packageName == app.packageName
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/models/AppItemWrapper.kt
================================================
package com.garage.aastream.models
/**
* Created by Endy Rubbin on 23.05.2019 18:59.
* For project: AAStream
*/
data class AppItemWrapper(val apps: ArrayList<AppItem>)
================================================
FILE: app/src/main/java/com/garage/aastream/receivers/ScreenLockReceiver.kt
================================================
package com.garage.aastream.receivers
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.Intent.*
import com.garage.aastream.interfaces.OnScreenLockCallback
import com.garage.aastream.utils.DevLog
/**
* Created by Endy Rubbin on 22.05.2019 13:06.
* For project: AAStream
*
* Used to listen for screen state changes
*/
class ScreenLockReceiver(
private val callback: OnScreenLockCallback
) : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
DevLog.d("Screen state changed: ${intent?.action}")
when(intent?.action) {
ACTION_USER_PRESENT -> callback.onScreenUnlocked()
ACTION_SCREEN_ON -> callback.onScreenOn()
ACTION_SCREEN_OFF -> callback.onScreenOff()
}
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/receivers/UsbStateReceiver.kt
================================================
package com.garage.aastream.receivers
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.hardware.usb.UsbManager
import com.garage.aastream.utils.DevLog
/**
* Created by Endy Rubbin on 05.06.2019 10:34.
* For project: AAStream
*/
class UsbStateReceiver(private val callback: UsbStateCallback) : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
DevLog.d("USB state changed: ${intent?.action}")
when (intent?.action) {
Intent.ACTION_POWER_DISCONNECTED,
UsbManager.ACTION_USB_ACCESSORY_DETACHED,
UsbManager.ACTION_USB_DEVICE_DETACHED -> callback.onUsbDisconnected()
}
}
/**
* Callback for Usb disconnect state
*/
interface UsbStateCallback {
/**
* Called when USB is disconnected
*/
fun onUsbDisconnected()
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/services/CarService.kt
================================================
package com.garage.aastream.services
import com.google.android.apps.auto.sdk.CarActivity
import com.google.android.apps.auto.sdk.CarActivityService
import com.garage.aastream.activities.CarMainActivity
/**
* Created by Endy Rubbin on 23.05.2019 12:25.
* For project: AAStream
*/
class CarService : CarActivityService() {
/**
* Called from Android Auto to start [CarMainActivity]
*/
@Suppress("UNCHECKED_CAST")
override fun getCarActivity(): Class<out CarActivity> {
return CarMainActivity::class.java
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/shell/ShellExecutor.kt
================================================
package com.garage.aastream.shell
import com.garage.aastream.utils.DevLog
import eu.chainfire.libsuperuser.Shell
/**
* Created by Endy Rubbin on 28.06.2019 15:35.
* For project: AAStream
*/
class ShellExecutor(private val command: String): Thread() {
override fun run() {
if (Shell.SU.available()) {
DevLog.d("Executing shell command: $command")
Shell.Pool.SU.run(command)
DevLog.d("Executed shell command: $command")
}
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/utils/Const.kt
================================================
package com.garage.aastream.utils
/**
* Created by Endy Rubbin on 22.05.2019 14:36.
* For project: AAStream
*/
object Const {
const val PREFERENCES_NAME = "AAStreamPrefs"
const val MAX_VALUE = 100
const val DEFAULT_ANIMATION_DELAY = 300L
const val DEFAULT_ANIMATION_DURATION = 300L
const val DEFAULT_SCREEN = 1
const val DEFAULT_TAP_METHOD = 0
const val DEFAULT_SHOW_SIDEBAR = true
const val REQUEST_MEDIA_PROJECTION_PERMISSION = 1337
const val CLICK_INTERVAL = 300
const val DEBUG_CLICK_COUNT = 10
}
================================================
FILE: app/src/main/java/com/garage/aastream/utils/DevLog.kt
================================================
package com.garage.aastream.utils
import android.util.Log
import com.garage.aastream.interfaces.OnLogCallback
import java.lang.reflect.Array
/**
* Created by Endy Rubbin on 22.05.2019 13:11.
* For project: AAStream
*/
@Suppress("unused")
object DevLog {
private var DEBUG = false
private var TAG = "DevLog"
private var callback: OnLogCallback? = null
/**
* Init logger with default TAG
*/
fun init() {
DEBUG = true
}
/**
* Init logger with TAG and set enabled / disabled
* @param tag String Log TAG
* @param debug boolean logging enabled / disabled
*/
fun init(tag: String, debug: Boolean = true) {
DEBUG = debug
TAG = tag
}
/**
* Set callback for log events
*/
fun setCallback(callback: OnLogCallback) {
this.callback = callback
}
/**
* Remove callback for log events
*/
fun removeCallback() {
this.callback = null
}
/**
* Logger - outputs log messages and delivers callback if set
*/
fun d(vararg msg: Any?) {
if (DEBUG) {
val stackTraces = Thread.currentThread().stackTrace
val stackTraceElement = stackTraces[3]
val lineNumber = stackTraceElement.lineNumber.toString()
var className = getClassName(stackTraceElement.className)
val extension = getClassName(stackTraceElement.fileName)
className =
if (className.contains("$")) className.split("\\$".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0] else className
val out = StringBuilder("($className.$extension:$lineNumber): ")
for (o in msg) {
if (o == null) {
out.append("NULL, ")
} else {
if (o.javaClass.isArray) {
out.append("[")
for (i in 0 until Array.getLength(o)) {
out.append(Array.get(o, i)).append(", ")
}
out.append("], ")
} else {
out.append(o.toString()).append(", ")
}
}
}
val log = out.substring(0, out.length - 2)
callback?.onLogWritten(log)
Log.d(TAG, log)
}
}
/**
* Returns current class name
* @param className String
* @return String
*/
private fun getClassName(className: String): String {
val parts = className.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
return parts[parts.size - 1]
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/utils/PhenotypePatcher.kt
================================================
package com.garage.aastream.utils
import android.content.Context
import com.garage.aastream.R
import com.garage.aastream.interfaces.OnPatchStatusCallback
import java.io.*
/**
* Created by Endy Rubbin on 26.05.2019 22:52.
* For project: AAStream
*
* Reference and credit: @see <a href="https://github.com/Eselter/AA-Phenotype-Patcher">AA-Phenotype-Patcher</a>
*/
class PhenotypePatcher(val context: Context) {
private val path: String = context.applicationInfo.dataDir
/**
* Runs DB injections to whitelist AA Stream application for Android Auto
*/
fun patch(callback: OnPatchStatusCallback) {
object : Thread() {
override fun run() {
var suitableMethodFound = true
copyAssets()
// Preserve already whitelisted apps and append AA Stream if not already whitelisted
var whiteList = runSuWithCmd("$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
"'SELECT stringVal FROM Flags WHERE packageName=\"com.google.android.gms.car#car\" LIMIT 1'"
).getInputStreamLog()
if (!whiteList.contains(context.applicationInfo.packageName)) {
whiteList += ",${context.applicationInfo.packageName}"
}
DevLog.d("Whitelisting apps: $whiteList")
DevLog.d(
runSuWithCmd(
"$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
"'DROP TRIGGER after_delete;'"
).getStreamLogsWithLabels()
)
DevLog.d(
runSuWithCmd(
"$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
"'DELETE FROM Flags WHERE name=\"app_white_list\";'"
).getStreamLogsWithLabels()
)
when {
runSuWithCmd(
"$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
"'SELECT 1 FROM Packages WHERE packageName=\"com.google.android.gms.car#car\"'"
).getInputStreamLog() == "1" -> {
DevLog.d(
runSuWithCmd(
("$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
"'INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
"234, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
"230, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
"234, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
"230, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car_setup\", " +
"234, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car_setup\", " +
"230, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
"(SELECT version FROM Packages WHERE packageName=\"com.google.android.gms.car#car\"), " +
"0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
"(SELECT version FROM Packages WHERE packageName=\"com.google.android.gms.car\"), " +
"0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car_setup\", " +
"(SELECT version FROM Packages WHERE packageName=\"com.google.android.gms.car#car\"), " +
"0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);'")
).getStreamLogsWithLabels()
)
DevLog.d(
runSuWithCmd(
("$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
"'CREATE TRIGGER after_delete AFTER DELETE\n" +
"ON Flags\n" +
"BEGIN\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
"(SELECT version FROM Packages WHERE packageName=\"com.google.android.gms.car#car\"), " +
"0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
"230, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
"234, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
"(SELECT version FROM Packages WHERE packageName=\"com.google.android.gms.car\"), " +
"0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
"230, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
"234, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car_setup\", " +
"(SELECT version FROM Packages WHERE packageName=\"com.google.android.gms.car#car\"), " +
"0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car_setup\", " +
"230, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car_setup\", " +
"234, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"END;'")
).getStreamLogsWithLabels()
)
}
runSuWithCmd(
"$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
"'SELECT 1 FROM Packages WHERE packageName=\"com.google.android.gms.car\"'"
).getInputStreamLog() == "1" -> {
DevLog.d(
runSuWithCmd(
("$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
"'INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
"234, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
"230, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
"234, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
"230, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car_setup\", " +
"234, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car_setup\", " +
"230, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
"(SELECT version FROM Packages WHERE packageName=\"com.google.android.gms.car\"), " +
"0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
"(SELECT version FROM Packages WHERE packageName=\"com.google.android.gms.car\"), " +
"0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car_setup\", " +
"(SELECT version FROM Packages WHERE packageName=\"com.google.android.gms.car\"), " +
"0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);'")
).getStreamLogsWithLabels()
)
DevLog.d(
runSuWithCmd(
("$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
"'CREATE TRIGGER after_delete AFTER DELETE\n" +
"ON Flags\n" +
"BEGIN\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
"(SELECT version FROM Packages WHERE packageName=\"com.google.android.gms.car\"), " +
"0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
"230, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
"234, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
"(SELECT version FROM Packages WHERE packageName=\"com.google.android.gms.car\"), " +
"0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
"230, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
"234, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car_setup\", " +
"(SELECT version FROM Packages WHERE packageName=\"com.google.android.gms.car\"), " +
"0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car_setup\", " +
"230, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car_setup\", " +
"234, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"END;'")
).getStreamLogsWithLabels()
)
}
runSuWithCmd(
("$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
"'SELECT 1 FROM ApplicationStates WHERE packageName=\"com.google.android.gms.car#car\"'")
).getInputStreamLog() == "1" -> {
DevLog.d(
runSuWithCmd(
("$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
"'INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
"240, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
"(SELECT version FROM ApplicationStates WHERE packageName=\"com.google.android.gms.car#car\"), " +
"0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
"240, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
"(SELECT version FROM ApplicationStates WHERE packageName=\"com.google.android.gms.car\"), " +
"0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);'")
).getStreamLogsWithLabels()
)
DevLog.d(
runSuWithCmd(
("$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
"'CREATE TRIGGER after_delete AFTER DELETE\n" +
"ON Flags\n" +
"BEGIN\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
"240, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car#car\", " +
"(SELECT version FROM ApplicationStates WHERE packageName=\"com.google.android.gms.car#car\"), " +
"0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
"240, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
"(SELECT version FROM ApplicationStates WHERE packageName=\"com.google.android.gms.car\"), " +
"0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"END;'")
).getStreamLogsWithLabels()
)
}
runSuWithCmd(
("$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
"'SELECT 1 FROM ApplicationStates WHERE packageName=\"com.google.android.gms.car\"'")
).getInputStreamLog() == "1" -> {
DevLog.d(
runSuWithCmd(
("$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
"'INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
"240, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
"(SELECT version FROM ApplicationStates WHERE packageName=\"com.google.android.gms.car\"), " +
"0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);'")
).getStreamLogsWithLabels()
)
DevLog.d(
runSuWithCmd(
("$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
"'CREATE TRIGGER after_delete AFTER DELETE\n" +
"ON Flags\n" +
"BEGIN\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
"240, 0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, " +
"user, name, stringVal, committed) VALUES (\"com.google.android.gms.car\", " +
"(SELECT version FROM ApplicationStates WHERE packageName=\"com.google.android.gms.car\"), " +
"0, 0, \"\", \"app_white_list\", \"" + whiteList + "\",1);\n" +
"END;'")
).getStreamLogsWithLabels()
)
}
else -> suitableMethodFound = false
}
if (suitableMethodFound && isPatched()) {
callback.onPatchSuccessful()
} else {
callback.onPatchFailed()
}
}
}.start()
}
/**
* Check if application is whitelisted
*/
fun isPatched(): Boolean {
val suAvailable = try {
Runtime.getRuntime().exec("su")
true
} catch (e: java.lang.Exception) {
false
}
val checkStep1 =
runSuWithCmd(
("$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
"'SELECT * FROM Flags WHERE name=\"app_white_list\";'")
)
val checkStep1Sorted = checkStep1.getInputStreamLog().split("\n")
checkStep1Sorted.sortedBy { it }
var checkStep1SortedToString = ""
for (s in checkStep1Sorted) {
checkStep1SortedToString += "\n" + s
}
checkStep1SortedToString.replaceFirst(("\n").toRegex(), "")
checkStep1.setInputStreamLog(checkStep1SortedToString)
runSuWithCmd(
"$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
"'DELETE FROM Flags WHERE name=\"app_white_list\";'"
)
val checkStep3 =
runSuWithCmd(
"$path/sqlite3 /data/data/com.google.android.gms/databases/phenotype.db " +
"'SELECT * FROM Flags WHERE name=\"app_white_list\";'"
)
val checkStep3Sorted = checkStep3.getInputStreamLog().split("\n")
checkStep3Sorted.sortedBy { it }
var checkStep3SortedToString = ""
for (s in checkStep3Sorted) {
checkStep3SortedToString += "\n" + s
}
checkStep3SortedToString.replaceFirst(("\n").toRegex(), "")
checkStep3.setInputStreamLog(checkStep3SortedToString)
return suAvailable && checkStep1.getInputStreamLog().isNotEmpty() && checkStep3.getInputStreamLog().isNotEmpty()
&& checkStep1.getInputStreamLog().length == checkStep3.getInputStreamLog().length
}
/**
* Execute SU cmd command and handle input/output streams
*/
fun runSuWithCmd(cmd: String): StreamLogs {
val outputStream: DataOutputStream?
val inputStream: InputStream?
val errorStream: InputStream?
val streamLogs = StreamLogs()
streamLogs.setOutputStreamLog(cmd)
try {
val su = Runtime.getRuntime().exec("su")
outputStream = DataOutputStream(su.outputStream)
inputStream = su.inputStream
errorStream = su.errorStream
outputStream.writeBytes(cmd + "\n")
outputStream.flush()
outputStream.writeBytes("exit\n")
outputStream.flush()
try {
su.waitFor()
} catch (e: InterruptedException) {
e.printStackTrace()
}
streamLogs.setInputStreamLog(readFully(inputStream))
streamLogs.setErrorStreamLog(readFully(errorStream))
} catch (e: IOException) {
e.printStackTrace()
}
return streamLogs
}
/**
* Initialize SQLite3 DB
*/
private fun copyAssets() {
val path = context.applicationInfo.dataDir
val file = File(path, "sqlite3")
if (!file.exists()) {
try {
val input = context.resources.openRawResource(R.raw.sqlite3)
val outDir = context.applicationInfo.dataDir
val outFile = File(outDir, "sqlite3")
val buffer = ByteArray(1024)
var length: Int
val out = FileOutputStream(outFile)
while (input.read(buffer).also { length = it } >= 0) {
out.write(buffer, 0, length)
}
input.close()
out.flush()
out.close()
} catch (e: IOException) {
DevLog.d("Failed to copy asset file: sqlite3", e)
}
}
DevLog.d(runSuWithCmd("chmod 775 $path/sqlite3").getStreamLogsWithLabels())
}
/**
* @return String value of cmd input
*/
private fun readFully(input: InputStream): String {
return try {
val output = ByteArrayOutputStream()
val buffer = ByteArray(1024)
var length: Int
while (input.read(buffer).also { length = it } >= 0) {
output.write(buffer, 0, length)
}
output.toString("UTF-8")
} catch (e: Exception) {
DevLog.d("Failed to read fully")
""
}
}
/**
* Wrapper class for CMD streams
*/
inner class StreamLogs {
private var inputStreamLog: String? = null
private var errorStreamLog: String? = null
private var outputStreamLog: String? = null
fun getInputStreamLog(): String {
return inputStreamLog?.trim() ?: ""
}
private fun getErrorStreamLog(): String {
return errorStreamLog?.trim() ?: ""
}
private fun getOutputStreamLog(): String {
return outputStreamLog?.trim() ?: ""
}
fun setInputStreamLog(inputStreamLog: String) {
this.inputStreamLog = inputStreamLog
}
fun setErrorStreamLog(errorStreamLog: String) {
this.errorStreamLog = errorStreamLog
}
fun setOutputStreamLog(outputStreamLog: String) {
this.outputStreamLog = outputStreamLog
}
private fun getInputStreamLogWithLabel(): String {
return "\tInputStream:\n\t\t" + getInputStreamLog().replace("\n".toRegex(), "\n\t\t")
}
private fun getErrorStreamLogWithLabel(): String {
return "\tErrorStream:\n\t\t" + getErrorStreamLog().replace("\n".toRegex(), "\n\t\t")
}
private fun getOutputStreamLogWithLabel(): String {
return "\tOutputStream:\n\t\t" + getOutputStreamLog().replace("\n".toRegex(), "\n\t\t")
}
fun getStreamLogsWithLabels(): String {
var result = "\n" + getOutputStreamLogWithLabel()
if (getInputStreamLog().isNotEmpty()) {
result += "\n" + getInputStreamLogWithLabel()
}
if (getErrorStreamLog().isNotEmpty()) {
result += "\n" + getErrorStreamLogWithLabel()
}
return result
}
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/views/AutoFitRecyclerView.kt
================================================
package com.garage.aastream.views
import android.content.Context
import android.util.AttributeSet
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
/**
* Created by Endy Rubbin on 23.05.2019 14:40.
* For project: AAStream
*/
class AutoFitRecyclerView : RecyclerView {
private var manager: GridLayoutManager? = null
private var columnWidth = -1
constructor(context: Context) : super(context) {
init(context, null)
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
init(context, attrs)
}
constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) {
init(context, attrs)
}
private fun init(context: Context, attrs: AttributeSet?) {
if (attrs != null) {
val attrsArray = intArrayOf(android.R.attr.columnWidth)
val array = context.obtainStyledAttributes(attrs, attrsArray)
columnWidth = array.getDimensionPixelSize(0, -1)
array.recycle()
}
manager = GridLayoutManager(getContext(), 1)
layoutManager = manager
}
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
super.onMeasure(widthSpec, heightSpec)
if (columnWidth > 0) {
val spanCount = Math.max(1, measuredWidth / columnWidth)
manager?.spanCount = spanCount
}
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/views/FingerTapDetector.kt
================================================
package com.garage.aastream.views
import android.content.Context
import android.util.ArrayMap
import android.view.MotionEvent
import android.view.MotionEvent.*
import android.view.ViewConfiguration
import com.garage.aastream.handlers.PreferenceHandler
import com.garage.aastream.interfaces.OnMenuTapCallback
import com.garage.aastream.utils.Const
/**
* Created by Endy Rubbin on 27.05.2019 13:34.
* For project: AAStream
*/
class FingerTapDetector(
context: Context,
preferenceHandler: PreferenceHandler,
callback: OnMenuTapCallback
) {
private var callback: OnMenuTapCallback? = null
private var eventMap: ArrayMap<Int, PointerCoords> = ArrayMap()
private var touchSlopSquare: Int = 0
private var previousTime: Long = 0
private var tapCount = 0
private val openMethod = preferenceHandler.getInt(PreferenceHandler.KEY_OPEN_MENU_METHOD, MenuOpenMethod.TWO_FINGER_TAP.value)
private val minTapCount = when (openMethod) {
MenuOpenMethod.DOUBLE_TAP.value -> 2
MenuOpenMethod.TRIPLE_TAP.value -> 3
else -> 0
}
init {
this.callback = callback
val configuration = ViewConfiguration.get(context)
val touchSlop = configuration.scaledTouchSlop
touchSlopSquare = touchSlop * touchSlop
}
fun removeCallback() {
this.callback = null
}
fun onTouchEvent(event: MotionEvent): Boolean {
val action = event.actionMasked
for (i in 0 until event.pointerCount) {
val id = event.getPointerId(i)
val coords = PointerCoords()
when (action) {
ACTION_DOWN, ACTION_POINTER_DOWN -> {
event.getPointerCoords(i, coords)
eventMap[id] = coords
}
ACTION_MOVE -> if (eventMap.containsKey(id)) {
event.getPointerCoords(i, coords)
eventMap[id]?.let {
val dist = getSquaredDistance(it, coords)
if (dist > touchSlopSquare) {
eventMap.remove(id)
}
}
}
ACTION_UP -> {
if (openMethod == MenuOpenMethod.TWO_FINGER_TAP.value && eventMap.size == 2) {
callback?.onTapForMenu()
}
eventMap.clear()
}
ACTION_CANCEL -> eventMap.remove(id)
}
}
if (event.action == ACTION_UP && minTapCount > 0) {
val currentTime = System.currentTimeMillis()
if (currentTime - previousTime <= Const.CLICK_INTERVAL) {
tapCount++
} else {
tapCount = 0
}
previousTime = currentTime
if (tapCount >= minTapCount) {
tapCount = 0
callback?.onTapForMenu()
}
}
return false
}
/**
* Calculate distance between taps
*/
private fun getSquaredDistance(p1: PointerCoords, p2: PointerCoords): Double {
val dx = (p1.x - p2.x).toDouble()
val dy = (p1.y - p2.y).toDouble()
return dx * dx + dy * dy
}
companion object {
enum class MenuOpenMethod(val value: Int) {
TWO_FINGER_TAP(0),
DOUBLE_TAP(1),
TRIPLE_TAP(2)
}
}
}
================================================
FILE: app/src/main/java/com/garage/aastream/views/MarginDecoration.kt
================================================
package com.garage.aastream.views
import android.content.Context
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.garage.aastream.R
/**
* Created by Endy Rubbin on 23.05.2019 14:49.
* For project: AAStream
*/
class MarginDecoration(context: Context) : RecyclerView.ItemDecoration() {
private var margin = context.resources.getDimensionPixelSize(R.dimen.item_margin)
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
outRect.set(0, margin, margin, margin)
}
}
================================================
FILE: app/src/main/res/drawable-anydpi/bg.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/bg_tile"
android:tileMode="repeat" />
================================================
FILE: app/src/main/res/drawable-anydpi/bg_input.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="5dp" />
<stroke android:color="@color/color_input_border" android:width="2dp" />
<solid android:color="@color/color_input_background" />
</shape>
================================================
FILE: app/src/main/res/drawable-anydpi/ic_apps.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFF"
android:pathData="M4,8h4L8,4L4,4v4zM10,20h4v-4h-4v4zM4,20h4v-4L4,16v4zM4,14h4v-4L4,10v4zM10,14h4v-4h-4v4zM16,
4v4h4L20,4h-4zM10,8h4L14,4h-4v4zM16,14h4v-4h-4v4zM16,20h4v-4h-4v4z"/>
</vector>
================================================
FILE: app/src/main/res/drawable-anydpi/ic_back.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFF"
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>
================================================
FILE: app/src/main/res/drawable-anydpi/ic_bookmark.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFC107"
android:pathData="M17,3H7c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3V5c0,-1.1 -0.9,-2 -2,-2z"/>
</vector>
================================================
FILE: app/src/main/res/drawable-anydpi/ic_bookmark_border.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFF"
android:pathData="M17,3L7,3c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3L19,5c0,-1.1 -0.9,-2 -2,-2zM17,18l-5,-2.18L7,
18L7,5h10v13z"/>
</vector>
================================================
FILE: app/src/main/res/drawable-anydpi/ic_check.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
</vector>
================================================
FILE: app/src/main/res/drawable-anydpi/ic_close.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFF"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59
13.41,12z"/>
</vector>
================================================
FILE: app/src/main/res/drawable-anydpi/ic_terminal.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFF"
android:pathData="M13,7h-2v2h2L13,7zM13,11h-2v6h2v-6zM17,1.01L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,
0 2,-0.9 2,-2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19L7,19L7,5h10v14z"/>
</vector>
================================================
FILE: app/src/main/res/layout/activity_car.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activities.CarMainActivity">
<!-- Used for mirroring device screen -->
<SurfaceView
android:id="@+id/car_surface_view"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<!-- Menu holder -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- App list -->
<com.garage.aastream.views.AutoFitRecyclerView
android:id="@+id/car_app_grid"
android:layout_width="0dp"
android:layout_height="match_parent"
android:columnWidth="@dimen/item_size"
android:background="@color/color_overlay_background"
android:visibility="gone"
app:spanCount="1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/car_menu_holder"
app:layout_constraintEnd_toEndOf="parent"
tools:visibility="visible"
tools:listitem="@layout/row_app_item">
</com.garage.aastream.views.AutoFitRecyclerView>
<ProgressBar
android:id="@+id/car_app_grid_loader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/car_menu_holder"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
android:id="@+id/car_app_favorite_empty"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/txt_favorite_list_empty"
style="@style/FavoriteTextStyle"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/car_menu_holder"
app:layout_constraintEnd_toEndOf="parent"/>
<!-- Debug view -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
android:id="@+id/view_car_terminal"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/car_menu_holder"
app:layout_constraintEnd_toEndOf="parent">
<include layout="@layout/view_car_terminal"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- Menu view -->
<ScrollView
android:id="@+id/car_menu_holder"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:background="@color/color_menu_background"
android:alpha="0"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:alpha="1">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp">
<ImageView
style="@style/IconStyle"
android:id="@+id/car_menu_close"
android:src="@drawable/ic_close"
android:background="?android:attr/selectableItemBackground"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/car_menu_back"
tools:ignore="ContentDescription"/>
<ImageView
style="@style/IconStyle"
android:id="@+id/car_menu_back"
android:src="@drawable/ic_back"
android:background="?android:attr/selectableItemBackground"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/car_menu_close"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/car_menu_app_list"
tools:ignore="ContentDescription"/>
<ImageView
style="@style/IconStyle"
android:id="@+id/car_menu_app_list"
android:src="@drawable/ic_apps"
android:background="?android:attr/selectableItemBackground"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/car_menu_back"
app:layout_constraintBottom_toTopOf="@id/car_menu_favorites"
app:layout_constraintStart_toStartOf="parent"
tools:ignore="ContentDescription"/>
<ImageView
style="@style/IconStyle"
android:id="@+id/car_menu_favorites"
android:src="@drawable/ic_bookmark_border"
android:background="?android:attr/selectableItemBackground"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/car_menu_app_list"
app:layout_constraintBottom_toTopOf="@id/car_menu_terminal"
app:layout_constraintStart_toStartOf="parent"
tools:ignore="ContentDescription"/>
<ImageView
style="@style/IconStyle"
android:id="@+id/car_menu_terminal"
android:src="@drawable/ic_terminal"
android:background="?android:attr/selectableItemBackground"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/car_menu_favorites"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:ignore="ContentDescription"
tools:visibility="visible"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: app/src/main/res/layout/activity_request_result.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@color/color_overlay_background"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: app/src/main/res/layout/activity_settings.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/settings_root"
android:background="@drawable/bg"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activities.SettingsActivity">
<LinearLayout
android:orientation="vertical"
android:paddingBottom="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- Unlock app control -->
<include layout="@layout/view_settings_unlock"
android:id="@+id/view_settings_unlock" />
<View style="@style/DividerStyle"/>
<!-- Brightness control -->
<include layout="@layout/view_settings_brightness"
android:id="@+id/view_settings_brightness" />
<View style="@style/DividerStyle"/>
<!-- Rotation control -->
<include layout="@layout/view_settings_rotation"
android:id="@+id/view_settings_rotation" />
<View style="@style/DividerStyle"/>
<!-- Resize control -->
<include layout="@layout/view_settings_resize"
android:id="@+id/view_settings_resize" />
<View style="@style/DividerStyle"/>
<!-- Immersive control -->
<include layout="@layout/view_settings_immersive"
android:id="@+id/view_settings_immersive" />
<View style="@style/DividerStyle"/>
<!-- Audio control -->
<include layout="@layout/view_settings_audio"
android:id="@+id/view_settings_audio" />
<View style="@style/DividerStyle"/>
<!-- Sidebar control -->
<include layout="@layout/view_settings_sidebar"
android:id="@+id/view_settings_sidebar" />
<View style="@style/DividerStyle"/>
<!-- Debug car app control -->
<include layout="@layout/view_settings_debug"
android:visibility="gone"
android:id="@+id/view_settings_debug"
tools:visibility="visible"/>
<!-- About -->
<include layout="@layout/view_settings_about"
android:id="@+id/view_settings_about" />
</LinearLayout>
</ScrollView>
================================================
FILE: app/src/main/res/layout/row_app_item.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="@dimen/item_size"
android:layout_height="@dimen/item_size"
android:alpha="0"
android:background="?android:attr/selectableItemBackground"
tools:alpha="1">
<ImageView
android:id="@+id/item_app_icon"
style="@style/AppIconStyle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/item_app_name"
app:layout_constraintTop_toTopOf="parent"
tools:src="@android:drawable/sym_def_app_icon"
tools:ignore="ContentDescription"/>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingTop="2dp"
android:paddingEnd="8dp"
android:paddingStart="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent">
<ImageView
android:id="@+id/item_app_favorite"
style="@style/BookmarkStyle"
android:src="@drawable/ic_bookmark"
android:layout_alignParentEnd="true"
tools:ignore="ContentDescription"
tools:visibility="visible"/>
</RelativeLayout>
<TextView
android:id="@+id/item_app_name"
style="@style/AppTextStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
tools:text="@string/app_name"
app:layout_constraintTop_toBottomOf="@id/item_app_icon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: app/src/main/res/layout/view_car_terminal.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/color_overlay_background"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- Console output -->
<ScrollView
android:id="@+id/terminal_scroller"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/terminal_footer">
<TextView
android:id="@+id/terminal_console"
tools:text="Console log\nline1\nline2"
android:padding="16dp"
android:textSize="12sp"
android:textColor="@color/color_terminal"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</ScrollView>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/terminal_footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#333"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/color_divider"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/terminal_input_holder"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/terminal_input_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:visibility="visible">
<EditText
android:padding="8dp"
android:textSize="16sp"
android:maxLines="6"
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp"
android:fontFamily="sans-serif"
android:hint="@string/txt_enter_a_command"
android:textColorHint="@color/color_terminal_hint"
android:textColor="@color/color_terminal_text"
android:id="@+id/terminal_input"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:inputType="textCapSentences|textMultiLine"
android:imeOptions="actionDone"
android:background="@drawable/bg_input"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/terminal_input_button"
app:layout_constraintHorizontal_chainStyle="spread_inside"
tools:ignore="Autofill" />
<ImageView
android:id="@+id/terminal_input_button"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="8dp"
android:src="@drawable/ic_check"
android:tint="@color/color_terminal_icon"
android:foreground="?android:attr/selectableItemBackground"
tools:ignore="ContentDescription,UnusedAttribute"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: app/src/main/res/layout/view_settings_about.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/settings_about_holder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginEnd="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/txt_about_title"
style="@style/TitleTextStyle"/>
<TextView
android:id="@+id/settings_about"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/txt_about"
android:drawableStart="@mipmap/ic_launcher_round"
android:drawablePadding="8dp"
style="@style/AboutTextStyle"/>
<TextView
android:id="@+id/settings_about_usage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoLink="web"
android:text="@string/txt_about_use"
style="@style/AboutTextStyle"/>
<TextView
android:id="@+id/settings_about_version"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/txt_version"
style="@style/AboutTextStyle"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/txt_endy_rubbin"
style="@style/AboutTextStyle"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: app/src/main/res/layout/view_settings_audio.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/settings_audio_holder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@id/settings_audio_switch"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/txt_force_audio_title"
style="@style/TitleTextStyle"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/txt_force_audio_subtitle"
style="@style/SubtitleTextStyle"/>
</LinearLayout>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/settings_audio_switch"
style="@style/SwitchStyle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/settings_audio_holder"
app:layout_constraintBottom_toBottomOf="@id/settings_audio_holder"
app:layout_constraintTop_toTopOf="@id/settings_audio_holder"/>
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: app/src/main/res/layout/view_settings_brightness.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/settings_brightness_holder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@id/settings_brightness_switch"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/txt_overwrite_screen_brightness_title"
style="@style/TitleTextStyle"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/txt_overwrite_screen_brightness_subtitle"
style="@style/SubtitleTextStyle"/>
</LinearLayout>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/settings_brightness_switch"
style="@style/SwitchStyle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/settings_brightness_holder"
app:layout_constraintBottom_toBottomOf="@id/settings_brightness_holder"
app:layout_constraintTop_toTopOf="@id/settings_brightness_holder"/>
<androidx.appcompat.widget.AppCompatSeekBar
android:id="@+id/settings_brightness_seek_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="-8dp"
android:layout_marginEnd="-8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/settings_brightness_holder" />
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: app/src/main/res/layout/view_settings_debug.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/settings_debug_holder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@id/settings_debug_switch"
app:layout_constraintBottom_toTopOf="@id/settings_debug_switch"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/txt_disable_developer_mode_title"
style="@style/TitleTextStyle"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/txt_disable_developer_mode_subtitle"
style="@style/SubtitleTextStyle"/>
</LinearLayout>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/settings_debug_switch"
android:layout_marginEnd="16dp"
style="@style/SwitchStyle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/settings_debug_holder"
app:layout_constraintBottom_toBottomOf="@id/settings_debug_holder"
app:layout_constraintTop_toTopOf="@id/settings_debug_holder"/>
<View style="@style/DividerStyle"
android:id="@+id/settings_debug_divider"
app:layout_constraintTop_toBottomOf="@id/settings_debug_holder"
app:layout_constraintBottom_toTopOf="@+id/settings_debug_activity_holder"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<LinearLayout
android:id="@+id/settings_debug_activity_holder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="16dp"
android:background="?android:attr/selectableItemBackground"
app:layout_constraintTop_toBottomOf="@id/settings_debug_holder"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="@string/txt_debug_car_activity_title"
style="@style/TitleTextStyle"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="@string/txt_debug_car_activity_subtitle"
style="@style/SubtitleTextStyle"/>
<View style="@style/DividerStyle"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: app/src/main/res/layout/view_settings_immersive.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/settings_immersive_holder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@id/settings_immersive_switch"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/txt_force_immersive_title"
style="@style/TitleTextStyle"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/txt_force_immersive_subtitle"
style="@style/SubtitleTextStyle"/>
</LinearLayout>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/settings_immersive_switch"
style="@style/SwitchStyle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/settings_immersive_holder"
app:layout_constraintBottom_toBottomOf="@id/settings_immersive_holder"
app:layout_constraintTop_toTopOf="@id/settings_immersive_holder"/>
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: app/src/main/res/layout/view_settings_resize.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/settings_resize_holder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@id/settings_resize_switch"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/txt_force_resize_title"
style="@style/TitleTextStyle"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/txt_force_resize_subtitle"
style="@style/SubtitleTextStyle"/>
</LinearLayout>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/settings_resize_switch"
style="@style/SwitchStyle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/settings_resize_holder"
app:layout_constraintBottom_toBottomOf="@id/settings_resize_holder"
app:layout_constraintTop_toTopOf="@id/settings_resize_holder"/>
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: app/src/main/res/layout/view_settings_rotation.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/settings_rotation_holder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@id/settings_rotation_switch"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/txt_force_screen_rotation_title"
style="@style/TitleTextStyle"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/txt_force_screen_rotation_subtitle"
style="@style/SubtitleTextStyle"/>
</LinearLayout>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/settings_rotation_switch"
style="@style/SwitchStyle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/settings_rotation_holder"
app:layout_constraintBottom_toBottomOf="@id/settings_rotation_holder"
app:layout_constraintTop_toTopOf="@id/settings_rotation_holder"/>
<androidx.appcompat.widget.AppCompatSpinner
android:id="@+id/settings_rotation_dropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/SpinnerStyle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/settings_rotation_holder"/>
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: app/src/main/res/layout/view_settings_sidebar.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:padding="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/settings_sidebar_holder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/settings_sidebar_dropdown_title"
app:layout_constraintEnd_toStartOf="@id/settings_sidebar_switch"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/txt_enable_sidebar_title"
style="@style/TitleTextStyle"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/txt_enable_sidebar_subtitle"
style="@style/SubtitleTextStyle"/>
</LinearLayout>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/settings_sidebar_switch"
style="@style/SwitchStyle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/settings_sidebar_holder"
app:layout_constraintBottom_toBottomOf="@id/settings_sidebar_holder"
app:layout_constraintTop_toTopOf="@id/settings_sidebar_holder"/>
<TextView
android:id="@+id/settings_sidebar_dropdown_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/txt_choose_startup_screen"
style="@style/TitleTextStyle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/settings_sidebar_dropdown"
app:layout_constraintTop_toBottomOf="@id/settings_sidebar_holder"/>
<androidx.appcompat.widget.AppCompatSpinner
android:id="@+id/settings_sidebar_dropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/SpinnerStyle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/settings_sidebar_dropdown_menu_title"
app:layout_constraintTop_toBottomOf="@id/settings_sidebar_dropdown_title"/>
<TextView
android:id="@+id/settings_sidebar_dropdown_menu_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/txt_choose_open_sidebar_method"
style="@style/TitleTextStyle"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/settings_sidebar_dropdown_menu"
app:layout_constraintTop_toBottomOf="@id/settings_sidebar_dropdown"/>
<androidx.appcompat.widget.AppCompatSpinner
android:id="@+id/settings_sidebar_dropdown_menu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/SpinnerStyle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/settings_sidebar_dropdown_menu_title"/>
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: app/src/main/res/layout/view_settings_unlock.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:padding="16dp"
android:background="?android:attr/selectableItemBackground"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/settings_unlock_holder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@id/settings_unlock_state_holder"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/txt_unlock_for_android_auto_title"
style="@style/TitleTextStyle"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/txt_unlock_for_android_auto_subtitle"
style="@style/SubtitleTextStyle"/>
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/settings_unlock_state_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/settings_unlock_holder"
app:layout_constraintBottom_toBottomOf="@id/settings_unlock_holder"
app:layout_constraintTop_toTopOf="@id/settings_unlock_holder">
<ProgressBar
android:id="@+id/settings_unlock_state_spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
tools:visibility="visible"/>
<ImageView
android:id="@+id/settings_unlock_state_icon"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_check"
android:tint="@color/color_unlocked"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
tools:visibility="visible"
tools:ignore="ContentDescription"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: app/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="color_primary">#607D8B</color>
<color name="color_primary_dark">#455A64</color>
<color name="color_accent">#795548</color>
<color name="color_title">#333</color>
<color name="color_subtitle">#aaa</color>
<color name="color_terminal">#fff</color>
<color name="color_terminal_icon">#dbdbdb</color>
<color name="color_terminal_text">#333</color>
<color name="color_terminal_hint">#aaa</color>
<color name="color_favorite">#fff</color>
<color name="color_divider">#333</color>
<color name="color
gitextract_rg_4i2rc/ ├── README.md ├── apk_releases/ │ ├── aa-stream-v1.0.6.apk │ └── aa-stream-v1.1.0.27.apk ├── app/ │ ├── build.gradle │ ├── libs/ │ │ └── aauto.aar │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── assets/ │ │ └── libs/ │ │ ├── arm64-v8a/ │ │ │ ├── minitouch │ │ │ └── minitouch-nopie │ │ ├── armeabi/ │ │ │ ├── minitouch │ │ │ └── minitouch-nopie │ │ ├── armeabi-v7a/ │ │ │ ├── minitouch │ │ │ └── minitouch-nopie │ │ ├── mips/ │ │ │ ├── minitouch │ │ │ └── minitouch-nopie │ │ ├── mips64/ │ │ │ ├── minitouch │ │ │ └── minitouch-nopie │ │ ├── x86/ │ │ │ ├── minitouch │ │ │ └── minitouch-nopie │ │ └── x86_64/ │ │ ├── minitouch │ │ └── minitouch-nopie │ ├── java/ │ │ └── com/ │ │ └── garage/ │ │ └── aastream/ │ │ ├── App.kt │ │ ├── activities/ │ │ │ ├── CarDebugActivity.kt │ │ │ ├── CarMainActivity.kt │ │ │ ├── KillerActivity.kt │ │ │ ├── ResultRequestActivity.kt │ │ │ ├── SettingsActivity.kt │ │ │ └── controllers/ │ │ │ ├── CarActivityController.kt │ │ │ └── TerminalController.kt │ │ ├── adapters/ │ │ │ └── AppListAdapter.kt │ │ ├── handlers/ │ │ │ ├── AppHandler.kt │ │ │ ├── AudioHandler.kt │ │ │ ├── BrightnessHandler.kt │ │ │ ├── DisplayHandler.kt │ │ │ ├── NotificationHandler.kt │ │ │ ├── PreferenceHandler.kt │ │ │ └── RotationHandler.kt │ │ ├── injection/ │ │ │ ├── GlideModule.kt │ │ │ ├── InjectionComponent.kt │ │ │ └── InjectionModule.kt │ │ ├── interfaces/ │ │ │ ├── OnAppClickedCallback.kt │ │ │ ├── OnAppListLoadedCallback.kt │ │ │ ├── OnLogCallback.kt │ │ │ ├── OnMenuTapCallback.kt │ │ │ ├── OnMinitouchCallback.kt │ │ │ ├── OnPatchStatusCallback.kt │ │ │ ├── OnRotationChangedCallback.kt │ │ │ └── OnScreenLockCallback.kt │ │ ├── minitouch/ │ │ │ ├── MiniTouchHandler.kt │ │ │ ├── MiniTouchSocket.kt │ │ │ └── MinitouchDaemon.kt │ │ ├── models/ │ │ │ ├── AppItem.kt │ │ │ └── AppItemWrapper.kt │ │ ├── receivers/ │ │ │ ├── ScreenLockReceiver.kt │ │ │ └── UsbStateReceiver.kt │ │ ├── services/ │ │ │ └── CarService.kt │ │ ├── shell/ │ │ │ └── ShellExecutor.kt │ │ ├── utils/ │ │ │ ├── Const.kt │ │ │ ├── DevLog.kt │ │ │ └── PhenotypePatcher.kt │ │ └── views/ │ │ ├── AutoFitRecyclerView.kt │ │ ├── FingerTapDetector.kt │ │ └── MarginDecoration.kt │ └── res/ │ ├── drawable-anydpi/ │ │ ├── bg.xml │ │ ├── bg_input.xml │ │ ├── ic_apps.xml │ │ ├── ic_back.xml │ │ ├── ic_bookmark.xml │ │ ├── ic_bookmark_border.xml │ │ ├── ic_check.xml │ │ ├── ic_close.xml │ │ └── ic_terminal.xml │ ├── layout/ │ │ ├── activity_car.xml │ │ ├── activity_request_result.xml │ │ ├── activity_settings.xml │ │ ├── row_app_item.xml │ │ ├── view_car_terminal.xml │ │ ├── view_settings_about.xml │ │ ├── view_settings_audio.xml │ │ ├── view_settings_brightness.xml │ │ ├── view_settings_debug.xml │ │ ├── view_settings_immersive.xml │ │ ├── view_settings_resize.xml │ │ ├── view_settings_rotation.xml │ │ ├── view_settings_sidebar.xml │ │ └── view_settings_unlock.xml │ ├── raw/ │ │ └── sqlite3 │ ├── values/ │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ids.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── xml/ │ └── automotive_app_desc.xml ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew └── settings.gradle
Condensed preview — 97 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (220K chars).
[
{
"path": "README.md",
"chars": 4186,
"preview": "# AA Stream\n \n## About\n**AA Stream** is an unofficial and unsupported device screen "
},
{
"path": "app/build.gradle",
"chars": 2099,
"preview": "apply plugin: 'com.android.application'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-kapt'\napply plugin: 'kotlin"
},
{
"path": "app/src/main/AndroidManifest.xml",
"chars": 3304,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xm"
},
{
"path": "app/src/main/java/com/garage/aastream/App.kt",
"chars": 1189,
"preview": "package com.garage.aastream\n\nimport android.app.Application\nimport android.content.res.Configuration\nimport com.garage.a"
},
{
"path": "app/src/main/java/com/garage/aastream/activities/CarDebugActivity.kt",
"chars": 1490,
"preview": "package com.garage.aastream.activities\n\nimport android.content.res.Configuration\nimport android.os.Bundle\nimport android"
},
{
"path": "app/src/main/java/com/garage/aastream/activities/CarMainActivity.kt",
"chars": 1914,
"preview": "package com.garage.aastream.activities\n\nimport android.content.res.Configuration\nimport android.os.Bundle\nimport com.gar"
},
{
"path": "app/src/main/java/com/garage/aastream/activities/KillerActivity.kt",
"chars": 1336,
"preview": "package com.garage.aastream.activities\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport "
},
{
"path": "app/src/main/java/com/garage/aastream/activities/ResultRequestActivity.kt",
"chars": 2137,
"preview": "package com.garage.aastream.activities\n\nimport android.content.Context\nimport android.content.Intent\nimport android.os.B"
},
{
"path": "app/src/main/java/com/garage/aastream/activities/SettingsActivity.kt",
"chars": 12218,
"preview": "package com.garage.aastream.activities\n\nimport android.annotation.TargetApi\nimport android.content.Intent\nimport android"
},
{
"path": "app/src/main/java/com/garage/aastream/activities/controllers/CarActivityController.kt",
"chars": 24707,
"preview": "package com.garage.aastream.activities.controllers\n\nimport android.animation.Animator\nimport android.animation.AnimatorL"
},
{
"path": "app/src/main/java/com/garage/aastream/activities/controllers/TerminalController.kt",
"chars": 1441,
"preview": "package com.garage.aastream.activities.controllers\n\nimport android.os.Handler\nimport android.os.Looper\nimport android.vi"
},
{
"path": "app/src/main/java/com/garage/aastream/adapters/AppListAdapter.kt",
"chars": 6153,
"preview": "package com.garage.aastream.adapters\n\nimport android.content.Context\nimport android.graphics.drawable.Drawable\nimport an"
},
{
"path": "app/src/main/java/com/garage/aastream/handlers/AppHandler.kt",
"chars": 2186,
"preview": "package com.garage.aastream.handlers\n\nimport android.content.Context\nimport android.content.pm.ApplicationInfo\nimport an"
},
{
"path": "app/src/main/java/com/garage/aastream/handlers/AudioHandler.kt",
"chars": 2122,
"preview": "package com.garage.aastream.handlers\n\nimport android.content.Context\nimport android.media.AudioManager.AUDIOFOCUS_GAIN\ni"
},
{
"path": "app/src/main/java/com/garage/aastream/handlers/BrightnessHandler.kt",
"chars": 2178,
"preview": "package com.garage.aastream.handlers\n\nimport android.content.Context\nimport android.os.Build\nimport android.provider.Set"
},
{
"path": "app/src/main/java/com/garage/aastream/handlers/DisplayHandler.kt",
"chars": 1256,
"preview": "package com.garage.aastream.handlers\n\nimport android.content.Context\nimport com.garage.aastream.shell.ShellExecutor\nimpo"
},
{
"path": "app/src/main/java/com/garage/aastream/handlers/NotificationHandler.kt",
"chars": 3433,
"preview": "package com.garage.aastream.handlers\n\nimport android.app.Notification\nimport android.app.NotificationChannel\nimport andr"
},
{
"path": "app/src/main/java/com/garage/aastream/handlers/PreferenceHandler.kt",
"chars": 2536,
"preview": "package com.garage.aastream.handlers\n\nimport android.app.Application\nimport android.content.Context\nimport com.garage.aa"
},
{
"path": "app/src/main/java/com/garage/aastream/handlers/RotationHandler.kt",
"chars": 2166,
"preview": "package com.garage.aastream.handlers\n\nimport android.content.Context\nimport android.os.Build\nimport android.provider.Set"
},
{
"path": "app/src/main/java/com/garage/aastream/injection/GlideModule.kt",
"chars": 266,
"preview": "package com.garage.aastream.injection\n\nimport com.bumptech.glide.annotation.GlideModule\nimport com.bumptech.glide.module"
},
{
"path": "app/src/main/java/com/garage/aastream/injection/InjectionComponent.kt",
"chars": 928,
"preview": "package com.garage.aastream.injection\n\nimport com.garage.aastream.App\nimport com.garage.aastream.activities.CarDebugActi"
},
{
"path": "app/src/main/java/com/garage/aastream/injection/InjectionModule.kt",
"chars": 2264,
"preview": "package com.garage.aastream.injection\n\nimport android.app.Application\nimport com.garage.aastream.activities.controllers."
},
{
"path": "app/src/main/java/com/garage/aastream/interfaces/OnAppClickedCallback.kt",
"chars": 451,
"preview": "package com.garage.aastream.interfaces\n\nimport com.garage.aastream.models.AppItem\n\n/**\n * Created by Endy Rubbin on 23.0"
},
{
"path": "app/src/main/java/com/garage/aastream/interfaces/OnAppListLoadedCallback.kt",
"chars": 420,
"preview": "package com.garage.aastream.interfaces\n\nimport com.garage.aastream.models.AppItem\n\n/**\n * Created by Endy Rubbin on 23.0"
},
{
"path": "app/src/main/java/com/garage/aastream/interfaces/OnLogCallback.kt",
"chars": 250,
"preview": "package com.garage.aastream.interfaces\n\n/**\n * Created by Endy Rubbin on 28.05.2019 13:35.\n * For project: AAStream\n */\n"
},
{
"path": "app/src/main/java/com/garage/aastream/interfaces/OnMenuTapCallback.kt",
"chars": 239,
"preview": "package com.garage.aastream.interfaces\n\n/**\n * Created by Endy Rubbin on 27.05.2019 13:35.\n * For project: AAStream\n */\n"
},
{
"path": "app/src/main/java/com/garage/aastream/interfaces/OnMinitouchCallback.kt",
"chars": 322,
"preview": "package com.garage.aastream.interfaces\n\n/**\n * Created by Endy Rubbin on 10.06.2019 14:20.\n * For project: AAStream\n */\n"
},
{
"path": "app/src/main/java/com/garage/aastream/interfaces/OnPatchStatusCallback.kt",
"chars": 313,
"preview": "package com.garage.aastream.interfaces\n\n/**\n * Created by Endy Rubbin on 27.05.2019 10:33.\n * For project: AAStream\n */\n"
},
{
"path": "app/src/main/java/com/garage/aastream/interfaces/OnRotationChangedCallback.kt",
"chars": 251,
"preview": "package com.garage.aastream.interfaces\n\n/**\n * Created by Endy Rubbin on 03.06.2019 16:38.\n * For project: AAStream\n */\n"
},
{
"path": "app/src/main/java/com/garage/aastream/interfaces/OnScreenLockCallback.kt",
"chars": 457,
"preview": "package com.garage.aastream.interfaces\n\n/**\n * Created by Endy Rubbin on 22.05.2019 13:06.\n * For project: AAStream\n */\n"
},
{
"path": "app/src/main/java/com/garage/aastream/minitouch/MiniTouchHandler.kt",
"chars": 9134,
"preview": "package com.garage.aastream.minitouch\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport andr"
},
{
"path": "app/src/main/java/com/garage/aastream/minitouch/MiniTouchSocket.kt",
"chars": 6281,
"preview": "package com.garage.aastream.minitouch\n\nimport android.graphics.Point\nimport android.net.LocalSocket\nimport android.net.L"
},
{
"path": "app/src/main/java/com/garage/aastream/minitouch/MinitouchDaemon.kt",
"chars": 569,
"preview": "package com.garage.aastream.minitouch\n\nimport android.os.AsyncTask\nimport com.garage.aastream.interfaces.OnMinitouchCall"
},
{
"path": "app/src/main/java/com/garage/aastream/models/AppItem.kt",
"chars": 421,
"preview": "package com.garage.aastream.models\n\nimport android.graphics.drawable.Drawable\n\n/**\n * Created by Endy Rubbin on 23.05.20"
},
{
"path": "app/src/main/java/com/garage/aastream/models/AppItemWrapper.kt",
"chars": 171,
"preview": "package com.garage.aastream.models\n\n/**\n * Created by Endy Rubbin on 23.05.2019 18:59.\n * For project: AAStream\n */\ndata"
},
{
"path": "app/src/main/java/com/garage/aastream/receivers/ScreenLockReceiver.kt",
"chars": 845,
"preview": "package com.garage.aastream.receivers\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport an"
},
{
"path": "app/src/main/java/com/garage/aastream/receivers/UsbStateReceiver.kt",
"chars": 938,
"preview": "package com.garage.aastream.receivers\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport an"
},
{
"path": "app/src/main/java/com/garage/aastream/services/CarService.kt",
"chars": 544,
"preview": "package com.garage.aastream.services\n\nimport com.google.android.apps.auto.sdk.CarActivity\nimport com.google.android.apps"
},
{
"path": "app/src/main/java/com/garage/aastream/shell/ShellExecutor.kt",
"chars": 488,
"preview": "package com.garage.aastream.shell\n\nimport com.garage.aastream.utils.DevLog\nimport eu.chainfire.libsuperuser.Shell\n\n/**\n "
},
{
"path": "app/src/main/java/com/garage/aastream/utils/Const.kt",
"chars": 544,
"preview": "package com.garage.aastream.utils\n\n/**\n * Created by Endy Rubbin on 22.05.2019 14:36.\n * For project: AAStream\n */\nobjec"
},
{
"path": "app/src/main/java/com/garage/aastream/utils/DevLog.kt",
"chars": 2686,
"preview": "package com.garage.aastream.utils\nimport android.util.Log\nimport com.garage.aastream.interfaces.OnLogCallback\n\nimport ja"
},
{
"path": "app/src/main/java/com/garage/aastream/utils/PhenotypePatcher.kt",
"chars": 32210,
"preview": "package com.garage.aastream.utils\n\nimport android.content.Context\nimport com.garage.aastream.R\nimport com.garage.aastrea"
},
{
"path": "app/src/main/java/com/garage/aastream/views/AutoFitRecyclerView.kt",
"chars": 1455,
"preview": "package com.garage.aastream.views\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.recyc"
},
{
"path": "app/src/main/java/com/garage/aastream/views/FingerTapDetector.kt",
"chars": 3422,
"preview": "package com.garage.aastream.views\n\nimport android.content.Context\nimport android.util.ArrayMap\nimport android.view.Motio"
},
{
"path": "app/src/main/java/com/garage/aastream/views/MarginDecoration.kt",
"chars": 606,
"preview": "package com.garage.aastream.views\n\nimport android.content.Context\nimport android.graphics.Rect\nimport android.view.View\n"
},
{
"path": "app/src/main/res/drawable-anydpi/bg.xml",
"chars": 174,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<bitmap xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sr"
},
{
"path": "app/src/main/res/drawable-anydpi/bg_input.xml",
"chars": 288,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <corners a"
},
{
"path": "app/src/main/res/drawable-anydpi/ic_apps.xml",
"chars": 490,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n androi"
},
{
"path": "app/src/main/res/drawable-anydpi/ic_back.xml",
"chars": 347,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "app/src/main/res/drawable-anydpi/ic_bookmark.xml",
"chars": 397,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n androi"
},
{
"path": "app/src/main/res/drawable-anydpi/ic_bookmark_border.xml",
"chars": 439,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n androi"
},
{
"path": "app/src/main/res/drawable-anydpi/ic_check.xml",
"chars": 379,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n androi"
},
{
"path": "app/src/main/res/drawable-anydpi/ic_close.xml",
"chars": 433,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n androi"
},
{
"path": "app/src/main/res/drawable-anydpi/ic_terminal.xml",
"chars": 494,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n androi"
},
{
"path": "app/src/main/res/layout/activity_car.xml",
"chars": 7753,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n xmlns:android=\"http://"
},
{
"path": "app/src/main/res/layout/activity_request_result.xml",
"chars": 361,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n xmlns:android=\"http://"
},
{
"path": "app/src/main/res/layout/activity_settings.xml",
"chars": 2382,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n "
},
{
"path": "app/src/main/res/layout/row_app_item.xml",
"chars": 2182,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n xmlns:android=\"http://"
},
{
"path": "app/src/main/res/layout/view_car_terminal.xml",
"chars": 4692,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n xmlns:android=\"http://"
},
{
"path": "app/src/main/res/layout/view_settings_about.xml",
"chars": 2359,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n xmlns:android=\"http://"
},
{
"path": "app/src/main/res/layout/view_settings_audio.xml",
"chars": 1693,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n xmlns:android=\"http://"
},
{
"path": "app/src/main/res/layout/view_settings_brightness.xml",
"chars": 2306,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n xmlns:android=\"http://"
},
{
"path": "app/src/main/res/layout/view_settings_debug.xml",
"chars": 3594,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n xmlns:android=\"http://"
},
{
"path": "app/src/main/res/layout/view_settings_immersive.xml",
"chars": 1725,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n xmlns:android=\"http://"
},
{
"path": "app/src/main/res/layout/view_settings_resize.xml",
"chars": 1701,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n xmlns:android=\"http://"
},
{
"path": "app/src/main/res/layout/view_settings_rotation.xml",
"chars": 2227,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n xmlns:android=\"http://"
},
{
"path": "app/src/main/res/layout/view_settings_sidebar.xml",
"chars": 3983,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n xmlns:android=\"http://"
},
{
"path": "app/src/main/res/layout/view_settings_unlock.xml",
"chars": 3152,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n xmlns:android=\"http://"
},
{
"path": "app/src/main/res/values/colors.xml",
"chars": 913,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"color_primary\">#607D8B</color>\n <color name=\"colo"
},
{
"path": "app/src/main/res/values/dimens.xml",
"chars": 146,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <dimen name=\"item_margin\">8dp</dimen>\n <dimen name=\"item_size\""
},
{
"path": "app/src/main/res/values/ids.xml",
"chars": 640,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <item type=\"id\" name=\"icon\" />\n <item type=\"id\" name=\"title\" /"
},
{
"path": "app/src/main/res/values/strings.xml",
"chars": 4477,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\">AA Stream</string>\n\n <string-array nam"
},
{
"path": "app/src/main/res/values/styles.xml",
"chars": 3955,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBa"
},
{
"path": "app/src/main/res/xml/automotive_app_desc.xml",
"chars": 289,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<automotiveApp xmlns:tools=\"http://schemas.android.com/tools\">\n <uses name=\"se"
},
{
"path": "build.gradle",
"chars": 684,
"preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n e"
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 233,
"preview": "#Wed May 22 10:28:41 EEST 2019\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER"
},
{
"path": "gradlew",
"chars": 5296,
"preview": "#!/usr/bin/env sh\n\n##############################################################################\n##\n## Gradle start up"
},
{
"path": "settings.gradle",
"chars": 15,
"preview": "include ':app'\n"
}
]
// ... and 19 more files (download for full content)
About this extraction
This page contains the full source code of the endyrubbin/AAStream GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 97 files (199.9 KB), approximately 47.4k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.