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