[
  {
    "path": ".github/workflows/main.yml",
    "content": "name: Build signed debug APK\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - master\n\njobs:\n  build-signed-debug-apk:\n    runs-on: ubuntu-latest\n    continue-on-error: true\n    steps:\n      - name: Check out repository\n        uses: actions/checkout@v2.3.4\n        with:\n          submodules: true\n      - name: Restore Gradle cache\n        uses: actions/cache@v4\n        with:\n          path: |\n            ~/.gradle/caches\n            ~/.gradle/wrapper\n          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}\n          restore-keys: ${{ runner.os }}-gradle-\n      - name: Set up Java 8\n        uses: actions/setup-java@v1.4.3\n        with:\n          java-version: 8\n      - name: Grant execution permission to Gradle Wrapper\n        run: chmod +x gradlew\n      - name: Build debug APK\n        run: ./gradlew assembleDebug\n      - name: Setup build tool version variable\n        shell: bash\n        run: |\n          BUILD_TOOL_VERSION=$(ls /usr/local/lib/android/sdk/build-tools/ | tail -n 1)\n          echo \"BUILD_TOOL_VERSION=$BUILD_TOOL_VERSION\" >> $GITHUB_ENV\n          echo Last build tool version is: $BUILD_TOOL_VERSION\n      - name: Sign debug APK\n        uses: r0adkll/sign-android-release@v1\n        id: sign-debug-apk\n        with:\n          releaseDirectory: app/build/outputs/apk/debug\n          signingKeyBase64: ${{ secrets.KEYSTORE }}\n          alias: ${{ secrets.KEY_ALIAS }}\n          keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}\n          keyPassword: ${{ secrets.KEY_PASSWORD }}\n        env:\n          BUILD_TOOLS_VERSION: ${{ env.BUILD_TOOL_VERSION }}\n      - name: Upload artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: Signed Debug APK\n          path: ${{ steps.sign-debug-apk.outputs.signedReleaseFile }}\n"
  },
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/workspace.xml\n/.idea/libraries\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n.idea\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"launcherclient\"]\n\tpath = launcherclient\n\turl = https://github.com/LawnchairLauncher/launcherclient.git\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "app/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nfinal def commitHash = { ->\n    final def stdout = new ByteArrayOutputStream()\n    exec {\n        commandLine 'git', 'rev-parse', '--short=7', 'HEAD'\n        standardOutput = stdout\n    }\n    stdout.toString().trim()\n}\n\nandroid {\n    compileSdkVersion 29\n    buildToolsVersion \"29.0.2\"\n\n    defaultConfig {\n        applicationId \"app.lawnchair.lawnfeed\"\n        minSdkVersion 21\n        targetSdkVersion 29\n\n        versionCode 1\n        versionName \"4.0+${commitHash()}\"\n\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n    }\n\n    applicationVariants.all { variant ->\n        variant.outputs.all {\n            outputFileName = \"Lawnfeed ${variant.versionName}.apk\"\n        }\n    }\n\n    buildTypes {\n        debug {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\ndependencies {\n    implementation fileTree(include: ['*.jar'], dir: 'libs')\n    implementation \"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version\"\n    implementation \"androidx.core:core:1.0.2\"\n    implementation('com.googlecode.json-simple:json-simple:1.1.1') {\n        exclude group: 'org.hamcrest', module: 'hamcrest-core'\n    }\n    implementation project(':launcherclient')\n}\n\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-android-extensions'\n"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in C:\\Users\\papho\\AppData\\Local\\Android\\Sdk/tools/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\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    package=\"app.lawnchair.lawnfeed\">\n\n    <!-- Let's break some rules... -->\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.VIBRATE\" />\n    <uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\" />\n\n    <permission\n        android:name=\"app.lawnchair.lawnfeed.CONNECT_SERVICE\"\n        android:label=\"Connect to Lawnfeed service\"\n        android:permissionGroup=\"app.lawnchair.lawnfeed.CONNECT_SERVICE\"\n        android:protectionLevel=\"signature\" />\n\n    <application\n        android:allowBackup=\"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        <service\n            android:name=\".LauncherClientProxyService\"\n            android:enabled=\"true\"\n            android:exported=\"true\" />\n\n        <service android:name=\"amirz.aidlbridge.BridgeService\">\n            <intent-filter>\n                <action android:name=\"com.android.launcher3.WINDOW_OVERLAY\" />\n                <data android:scheme=\"app\" />\n            </intent-filter>\n        </service>\n\n        <!-- Required for Lawnfeed updater -->\n        <receiver\n            android:name=\".receivers.UpdateReceiver\" />\n        <activity android:name=\".PermissionActivity\"\n            android:theme=\"@style/Theme.Transparent\"\n            android:autoRemoveFromRecents=\"true\"\n            android:launchMode=\"singleTask\"\n            android:alwaysRetainTaskState=\"false\">\n        </activity>\n        <provider\n            android:name=\"androidx.core.content.FileProvider\"\n            android:authorities=\"app.lawnchair.lawnfeed.provider\"\n            android:exported=\"false\"\n            android:grantUriPermissions=\"true\">\n            <meta-data\n                android:name=\"android.support.FILE_PROVIDER_PATHS\"\n                android:resource=\"@xml/file_paths\" />\n        </provider>\n    </application>\n\n</manifest>"
  },
  {
    "path": "app/src/main/aidl/amirz/aidlbridge/IBridge.aidl",
    "content": "package amirz.aidlbridge;\n\nimport amirz.aidlbridge.IBridgeCallback;\n\ninterface IBridge {\n    oneway void bindService(in IBridgeCallback cb, in int flags);\n}\n"
  },
  {
    "path": "app/src/main/aidl/amirz/aidlbridge/IBridgeCallback.aidl",
    "content": "package amirz.aidlbridge;\n\ninterface IBridgeCallback {\n    oneway void onServiceConnected(in ComponentName name, in IBinder service);\n\n    oneway void onServiceDisconnected(in ComponentName name);\n}\n"
  },
  {
    "path": "app/src/main/java/amirz/aidlbridge/BridgeImpl.java",
    "content": "package amirz.aidlbridge;\n\nimport android.content.ComponentName;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.ServiceConnection;\nimport android.net.Uri;\nimport android.os.IBinder;\nimport android.os.Process;\nimport android.os.RemoteException;\nimport android.text.TextUtils;\nimport android.util.Log;\n\nimport java.util.HashSet;\nimport java.util.Set;\n\nimport app.lawnchair.lawnfeed.bridge.TransactProxy;\n\nclass BridgeImpl extends IBridge.Stub {\n    private static final String TAG = \"BridgeImpl\";\n\n    private final Context mContext;\n    private final String mPackage;\n    private final Intent mIntent;\n    private final Set<ServiceConnection> mConnections = new HashSet<>();\n\n    BridgeImpl(Context context, Intent intent) {\n        mContext = context;\n\n        Uri caller = intent.getData();\n        String authority = caller.getEncodedAuthority();\n        mPackage = TextUtils.isEmpty(authority) ? \"\" : authority.split(\":\")[0];\n\n        mIntent = intent.cloneFilter();\n        mIntent.setPackage(\"com.google.android.googlequicksearchbox\");\n        String auth = context.getPackageName() + \":\" + Process.myUid();\n        mIntent.setData(caller.buildUpon().encodedAuthority(auth).build());\n    }\n\n    String getPackage() {\n        return mPackage;\n    }\n\n    @Override\n    public void bindService(final IBridgeCallback cb, int flags) {\n        Log.e(TAG, \"Connect request from \" + getPackage());\n        ServiceConnection connection = new ServiceConnection() {\n            private boolean mConnected;\n\n            @Override\n            public void onServiceConnected(ComponentName name, IBinder service) {\n                if (!mConnected) {\n                    mConnected = true;\n                    Log.e(TAG, \"Connected for \" + mPackage);\n                    try {\n                        cb.onServiceConnected(name, new TransactProxy(service, mContext));\n                    } catch (RemoteException e) {\n                        e.printStackTrace();\n                    }\n                }\n            }\n\n            @Override\n            public void onServiceDisconnected(ComponentName name) {\n                if (mConnected) {\n                    mConnected = false;\n                    Log.e(TAG, \"Disconnected for \" + mPackage);\n                    try {\n                        cb.onServiceDisconnected(name);\n                    } catch (RemoteException e) {\n                        e.printStackTrace();\n                    }\n                }\n            }\n        };\n\n        if (mContext.bindService(mIntent, connection, flags)) {\n            mConnections.add(connection);\n        }\n    }\n\n    void disconnect() {\n        Log.e(TAG, \"Disconnect \" + mPackage + \" with \" + mConnections.size() + \" connections\");\n        for (ServiceConnection connection : mConnections) {\n            mContext.unbindService(connection);\n            connection.onServiceDisconnected(null);\n        }\n        mConnections.clear();\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/amirz/aidlbridge/BridgeService.java",
    "content": "package amirz.aidlbridge;\n\nimport android.app.Service;\nimport android.content.Intent;\nimport android.net.Uri;\nimport android.os.IBinder;\nimport android.util.Log;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class BridgeService extends Service {\n    private static final String TAG = \"BridgeService\";\n\n    private static String sLastConnection;\n    private final Map<Intent, BridgeImpl> mBridges = new HashMap<>();\n\n    @Override\n    public IBinder onBind(Intent intent) {\n        Uri caller = intent.getData();\n        if (caller != null) {\n            Log.e(TAG, \"Bind from \" + caller.toString());\n            if (!mBridges.containsKey(intent)) {\n                mBridges.put(intent, new BridgeImpl(getApplicationContext(), intent) {\n                    @Override\n                    public void bindService(final IBridgeCallback cb, int flags) {\n                        sLastConnection = getPackage();\n                        for (BridgeImpl bridge : mBridges.values()) {\n                            if (bridge != this) {\n                                bridge.disconnect();\n                            }\n                        }\n                        super.bindService(cb, flags);\n                    }\n                });\n            }\n            return mBridges.get(intent);\n        }\n        return null;\n    }\n\n    @Override\n    public boolean onUnbind(Intent intent) {\n        Uri caller = intent.getData();\n        if (caller != null) {\n            Log.e(TAG, \"Unbind from \" + caller.toString());\n            if (mBridges.containsKey(intent)) {\n                mBridges.remove(intent).disconnect();\n            }\n        }\n        return super.onUnbind(intent);\n    }\n\n    public static String getLastConnection() {\n        return sLastConnection;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/amirz/aidlbridge/TransactProxy.java",
    "content": "package amirz.aidlbridge;\n\nimport android.os.Binder;\nimport android.os.IBinder;\nimport android.os.Parcel;\nimport android.os.RemoteException;\n\nclass TransactProxy extends Binder {\n    private final IBinder mTarget;\n\n    TransactProxy(IBinder target) {\n        mTarget = target;\n    }\n\n    @Override\n    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)\n            throws RemoteException {\n        return mTarget.transact(code, data, reply, flags);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/app/lawnchair/lawnfeed/LauncherClientProxyService.kt",
    "content": "package app.lawnchair.lawnfeed\n\nimport android.app.Service\nimport android.content.Intent\nimport android.os.IBinder\n\nclass LauncherClientProxyService : Service() {\n\n    override fun onBind(intent: Intent): IBinder? {\n        return getBinder()\n    }\n\n    override fun onUnbind(intent: Intent?): Boolean {\n        binder?.onUnbind()\n        binder = null\n        stopSelf()\n        return super.onUnbind(intent)\n    }\n\n    private fun getBinder(): ProxyImpl {\n        if (binder == null) {\n            binder = ProxyImpl(applicationContext)\n        }\n        return binder!!\n    }\n\n    private var binder: ProxyImpl? = null\n}\n"
  },
  {
    "path": "app/src/main/java/app/lawnchair/lawnfeed/PermissionActivity.java",
    "content": "package app.lawnchair.lawnfeed;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.os.ResultReceiver;\n\nimport androidx.core.app.ActivityCompat;\nimport androidx.core.content.ContextCompat;\n\npublic class PermissionActivity extends Activity {\n    public static final int REQUEST_CODE = 1337;\n\n    private ResultReceiver resultReceiver;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n\n        // Don't continue if the activity doesn't have an intent\n        if (getIntent() == null) {\n            finish();\n            return;\n        }\n\n        // Get permissions array which we want to request\n        resultReceiver = getIntent().getParcelableExtra(\"resultReceiver\");\n        String[] permissionsArray = getIntent().getStringArrayExtra(\"permissions\");\n        int requestCode = getIntent().getIntExtra(\"requestCode\", REQUEST_CODE);\n\n        // Check if those permissions are already granted\n        if (PermissionResponse.hasPermissions(this, permissionsArray)) {\n            // Proceed like those permissions have been now granted\n            onComplete(requestCode, permissionsArray, new int[]{ PackageManager.PERMISSION_GRANTED });\n        } else {\n            // Otherwise request those permissions and wait for users response\n            ActivityCompat.requestPermissions(this, permissionsArray, requestCode);\n        }\n    }\n\n    private void onComplete(int requestCode, String[] permissions, int[] grantResult) {\n        Bundle bundle = new Bundle();\n        bundle.putStringArray(\"permissions\", permissions);\n        bundle.putIntArray(\"grantResult\", grantResult);\n        bundle.putInt(\"requestCode\", requestCode);\n\n        // Send our callback to the result receiver\n        resultReceiver.send(requestCode, bundle);\n        finish();\n    }\n\n    @Override\n    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResult) {\n        super.onRequestPermissionsResult(requestCode, permissions, grantResult);\n        onComplete(requestCode, permissions, grantResult);\n    }\n\n    public static void callAsync(Context context, String[] permissions, int requestCode, final PermissionResultCallback callback) {\n        // Proceed to the callback if permissions were already granted\n        if (PermissionResponse.hasPermissions(context, permissions)) {\n            callback.onComplete(new PermissionResponse(permissions, new int[]{PackageManager.PERMISSION_GRANTED}, requestCode));\n            return;\n        }\n\n        // Our result receiver to get the response asynchronously\n        ResultReceiver receiver = new ResultReceiver(new Handler(Looper.getMainLooper())) {\n            @Override\n            protected void onReceiveResult(int resultCode, Bundle resultData) {\n                super.onReceiveResult(resultCode, resultData);\n                int[] grantResult = resultData.getIntArray(\"grantResult\");\n                String[] permissions = resultData.getStringArray(\"permissions\");\n\n                // Call the callback with the result\n                callback.onComplete(new PermissionResponse(permissions, grantResult, resultCode));\n            }\n        };\n\n        // Build our intent to launch\n        Intent intent = new Intent(context, PermissionActivity.class);\n        intent.putExtra(\"requestCode\", requestCode);\n        intent.putExtra(\"permissions\", permissions);\n        intent.putExtra(\"resultReceiver\", receiver);\n        intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);\n\n        // Start our activity and wait\n        context.startActivity(intent);\n    }\n\n    // Contains results from requesting the permissions\n    public static class PermissionResponse {\n        private String[] permissions;\n        private int [] grantResult;\n        private int requestCode;\n\n        public PermissionResponse(String[] permissions, int[] grantResult, int requestCode) {\n            this.permissions = permissions;\n            this.grantResult = grantResult;\n            this.requestCode = requestCode;\n        }\n\n        public boolean isGranted() {\n            return (grantResult != null && grantResult.length > 0 && grantResult[0] == PackageManager.PERMISSION_GRANTED);\n        }\n\n        public String[] getPermissions() {\n            return permissions;\n        }\n\n        public int[] getGrantResult() {\n            return grantResult;\n        }\n\n        public int getRequestCode() {\n            return requestCode;\n        }\n\n        public static boolean hasPermissions(Context context, String[] permissionsArray) {\n            // If a permission isn't granted => return false\n            for (String permission : permissionsArray) {\n                if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED){\n                    return false;\n                }\n            }\n\n            // Return true only when all permissions are granted\n            return true;\n        }\n    }\n\n    // Simple interface to handle our async callbacks\n    public interface PermissionResultCallback {\n        void onComplete(PermissionResponse response);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/app/lawnchair/lawnfeed/ProxyImpl.kt",
    "content": "package app.lawnchair.lawnfeed\n\nimport android.content.*\nimport android.content.pm.PackageManager\nimport android.net.Uri\nimport android.os.Binder\nimport android.os.Bundle\nimport android.os.IBinder\nimport android.os.Process\nimport android.util.Log\nimport app.lawnchair.launcherclient.ILauncherClientProxy\nimport app.lawnchair.launcherclient.ILauncherClientProxyCallback\nimport app.lawnchair.launcherclient.LauncherClientProxyConnection\nimport app.lawnchair.launcherclient.WindowLayoutParams\nimport app.lawnchair.lawnfeed.bridge.TransactProxy\nimport app.lawnchair.lawnfeed.updater.Updater\nimport com.google.android.libraries.launcherclient.ILauncherOverlay\nimport com.google.android.libraries.launcherclient.ILauncherOverlayCallback\n\nclass ProxyImpl(val context: Context) : ILauncherClientProxy.Stub() {\n    private lateinit var proxyCallback: ILauncherClientProxyCallback\n    private var overlayCallbacks = OverlayCallbacks()\n    private var destroyed = false\n    private var serviceConnected: Boolean = false\n    private var overlay: ILauncherOverlay? = null\n    private var allowed = false\n    private val serviceConnection = OverlayServiceConnection()\n    private val serviceIntent = ProxyImpl.getServiceIntent(context)\n    private var serviceStatus: Int = 0\n\n    override fun reconnect(): Int {\n        enforcePermission()\n        try {\n            if (destroyed || serviceStatus != 0)\n                return serviceStatus\n\n            if (sApplicationConnection != null && sApplicationConnection?.packageName != serviceIntent.`package`)\n                context.unbindService(sApplicationConnection)\n\n            if (sApplicationConnection == null) {\n                sApplicationConnection = AppServiceConnection(serviceIntent.`package`)\n\n                if (!connectSafely(context, sApplicationConnection!!, Context.BIND_WAIVE_PRIORITY)) {\n                    sApplicationConnection = null\n                }\n            }\n\n            if (sApplicationConnection != null) {\n                serviceStatus = LauncherClientProxyConnection.SERVICE_CONNECTING\n\n                if (!connectSafely(context, serviceConnection, Context.BIND_ADJUST_WITH_ACTIVITY)) {\n                    serviceStatus = LauncherClientProxyConnection.SERVICE_DISCONNECTED\n                } else {\n                    serviceConnected = true\n                }\n            }\n\n            if (serviceStatus == 0) {\n                proxyCallback.overlayStatusChanged(0)\n            }\n        } catch (e: Exception) {\n            Log.d(TAG, \"error reconnecting\", e)\n        }\n\n        return serviceStatus\n    }\n\n    private fun connectSafely(context: Context, conn: ServiceConnection, flags: Int): Boolean {\n        try {\n            return context.bindService(serviceIntent, conn, flags or Context.BIND_AUTO_CREATE)\n        } catch (e: SecurityException) {\n            Log.e(\"DrawerOverlayClient\", \"Unable to connect to overlay service\")\n            return false\n        }\n\n    }\n\n    override fun closeOverlay(options: Int) {\n        enforcePermission()\n        overlay?.closeOverlay(options)\n    }\n\n    override fun endScroll() {\n        enforcePermission()\n        overlay?.endScroll()\n    }\n\n    override fun onPause() {\n        enforcePermission()\n        overlay?.onPause()\n    }\n\n    override fun onResume() {\n        enforcePermission()\n        overlay?.onResume()\n    }\n\n    override fun onScroll(progress: Float) {\n        enforcePermission()\n        overlay?.onScroll(progress)\n    }\n\n    override fun openOverlay(options: Int) {\n        enforcePermission()\n        overlay?.openOverlay(options)\n    }\n\n    override fun startScroll() {\n        enforcePermission()\n        overlay?.startScroll()\n    }\n\n    override fun windowAttached(attrs: WindowLayoutParams, options: Int) {\n        enforcePermission()\n        overlay?.windowAttached(attrs.layoutParams, overlayCallbacks, options)\n    }\n\n    override fun windowAttached2(bundle: Bundle) {\n        enforcePermission()\n        overlay?.windowAttached2(bundle, overlayCallbacks)\n    }\n\n    override fun setActivityState(activityState: Int) {\n        enforcePermission()\n        overlay?.setActivityState(activityState)\n    }\n\n    override fun windowDetached(isChangingConfigurations: Boolean) {\n        enforcePermission()\n        overlay?.windowDetached(isChangingConfigurations)\n    }\n\n    override fun onQsbClick(intent: Intent?) {\n        enforcePermission()\n        context.sendOrderedBroadcast(intent, null, object : BroadcastReceiver() {\n\n            @Suppress(\"NAME_SHADOWING\")\n            override fun onReceive(context: Context?, intent: Intent?) {\n                proxyCallback.onQsbResult(resultCode)\n            }\n        }, null, 0, null, null)\n    }\n\n    override fun requestVoiceDetection(start: Boolean) {\n        enforcePermission()\n        overlay?.requestVoiceDetection(start)\n    }\n\n    override fun hasOverlayContent(): Boolean {\n        enforcePermission()\n        return overlay?.hasOverlayContent() ?: false\n    }\n\n    override fun startSearch(data: ByteArray?, bundle: Bundle?): Boolean {\n        enforcePermission()\n        return overlay?.startSearch(data, bundle) ?: false\n    }\n\n    override fun isVoiceDetectionRunning(): Boolean {\n        enforcePermission()\n        return overlay?.isVoiceDetectionRunning ?: false\n    }\n\n    override fun getVoiceSearchLanguage(): String {\n        enforcePermission()\n        return overlay?.voiceSearchLanguage ?: \"\"\n    }\n\n    override fun init(callback: ILauncherClientProxyCallback): Int {\n        allowed = callingPackage in TransactProxy.allowedPackages\n        enforcePermission()\n        proxyCallback = callback\n        Updater.checkUpdate(context)\n        ProxyImpl.getVersion(context)\n        return version\n    }\n\n    private val callingPackage get() = context.packageManager.getNameForUid(Binder.getCallingUid())\n\n    fun enforcePermission() {\n        if (!allowed)\n            throw SecurityException(\"$callingPackage is not allowed to call this service\")\n    }\n\n    inner class OverlayCallbacks : ILauncherOverlayCallback.Stub() {\n        override fun overlayScrollChanged(progress: Float) {\n            if (!destroyed)\n                proxyCallback.overlayScrollChanged(progress)\n        }\n\n        override fun overlayStatusChanged(status: Int) {\n            if (!destroyed)\n                proxyCallback.overlayStatusChanged(status)\n        }\n\n    }\n\n    internal inner class AppServiceConnection(val packageName: String) : ServiceConnection {\n\n        override fun onServiceConnected(name: ComponentName, service: IBinder) {\n\n        }\n\n        override fun onServiceDisconnected(name: ComponentName) {\n            if (name.packageName == packageName) {\n                sApplicationConnection = null\n            }\n        }\n    }\n\n    private inner class OverlayServiceConnection : ServiceConnection {\n        override fun onServiceConnected(name: ComponentName, service: IBinder) {\n            overlay = ILauncherOverlay.Stub.asInterface(service)\n            serviceStatus = LauncherClientProxyConnection.SERVICE_CONNECTED\n            proxyCallback.onServiceConnected()\n        }\n\n        override fun onServiceDisconnected(name: ComponentName) {\n            overlay = null\n            serviceStatus = LauncherClientProxyConnection.SERVICE_DISCONNECTED\n            proxyCallback.onServiceDisconnected()\n        }\n    }\n\n    companion object {\n        const val TAG = \"ProxyImpl\"\n\n        private var sApplicationConnection: AppServiceConnection? = null\n        private var version = -1\n\n        internal fun getServiceIntent(context: Context): Intent {\n            val uri = Uri.parse(\"app://${context.packageName}:${Process.myUid()}\").buildUpon()\n                    .appendQueryParameter(\"v\", Integer.toString(5))\n                    .build()\n\n            return Intent(\"com.android.launcher3.WINDOW_OVERLAY\")\n                    .setPackage(\"com.google.android.googlequicksearchbox\")\n                    .setData(uri)\n        }\n\n        private fun getVersion(context: Context) {\n            val resolveService = context.packageManager.resolveService(getServiceIntent(context), PackageManager.GET_META_DATA)\n            version = resolveService?.serviceInfo?.metaData?.getInt(\"service.api.version\", 1) ?: 1\n            Log.v(\"LauncherClient\", \"version: $version\")\n        }\n    }\n\n    fun onUnbind() {\n        Log.d(TAG, \"onUnbind\")\n        destroyed = true\n        if (serviceConnected)\n            context.unbindService(serviceConnection)\n        if (sApplicationConnection != null)\n            context.unbindService(sApplicationConnection)\n        sApplicationConnection = null\n    }\n}"
  },
  {
    "path": "app/src/main/java/app/lawnchair/lawnfeed/bridge/TransactProxy.kt",
    "content": "package app.lawnchair.lawnfeed.bridge\n\nimport android.content.Context\nimport android.content.pm.PackageManager.PERMISSION_GRANTED\nimport android.os.Binder\nimport android.os.IBinder\nimport android.os.Parcel\nimport app.lawnchair.lawnfeed.Manifest\n\nclass TransactProxy(private val target: IBinder, private val context: Context) : Binder() {\n\n    private val permissionGranted by lazy {\n        context.checkCallingPermission(Manifest.permission.CONNECT_SERVICE) == PERMISSION_GRANTED }\n    private val allowed by lazy { permissionGranted || callingPackage in allowedPackages }\n    private val callingPackage get() = context.packageManager.getNameForUid(getCallingUid())\n\n    override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {\n        enforcePermission()\n        return target.transact(code, data, reply, flags)\n    }\n\n    private fun enforcePermission() {\n        if (!allowed)\n            throw SecurityException(\"$callingPackage is not allowed to call this service\")\n    }\n\n    companion object {\n        @JvmStatic\n        val allowedPackages = setOf(\n                \"app.lawnchair\",\n                \"app.lawnchair.play\",\n                \"app.lawnchair.nightly\",\n                \"ch.deletescape.lawnchair.plah\",\n                \"ch.deletescape.lawnchair\"\n        )\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/app/lawnchair/lawnfeed/receivers/DownloadReceiver.java",
    "content": "package app.lawnchair.lawnfeed.receivers;\n\nimport android.app.DownloadManager;\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.database.Cursor;\nimport android.net.Uri;\nimport android.widget.Toast;\n\nimport app.lawnchair.lawnfeed.R;\n\npublic abstract class DownloadReceiver extends BroadcastReceiver {\n    public String mFilename;\n    private long downloadId;\n\n    @Override\n    public void onReceive(Context context, Intent intent) {\n        DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);\n        String action = intent.getAction();\n\n        // We only want to check if the download has completed\n        if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {\n            DownloadManager.Query query = new DownloadManager.Query();\n            query.setFilterById(downloadId);\n\n            // Check if file has been successfully downloaded\n            Cursor c = downloadManager.query(query);\n            if (c.moveToFirst()) {\n                int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);\n                int status = c.getInt(columnIndex);\n                switch (status) {\n                    // If everything is fine, call the abstract method and proceed\n                    case DownloadManager.STATUS_SUCCESSFUL:\n                        Uri uri = Uri.parse(c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)));\n                        onDownloadDone(uri);\n                        break;\n\n                    // Otherwise tell the user that the download failed\n                    default:\n                        Toast.makeText(context, R.string.download_file_error, Toast.LENGTH_LONG);\n                        break;\n                }\n\n                context.unregisterReceiver(this);\n            }\n\n            // Close any open resources\n            c.close();\n        }\n    }\n\n    public void setDownloadId(long id) {\n        this.downloadId = id;\n    }\n\n    public void setFilename(String filename) {\n        this.mFilename = filename;\n    }\n\n    public abstract void onDownloadDone(Uri uri);\n}\n"
  },
  {
    "path": "app/src/main/java/app/lawnchair/lawnfeed/receivers/UpdateReceiver.java",
    "content": "package app.lawnchair.lawnfeed.receivers;\n\nimport android.Manifest;\nimport android.app.DownloadManager;\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Environment;\nimport android.util.Log;\nimport android.widget.Toast;\n\nimport androidx.core.content.FileProvider;\n\nimport java.io.File;\n\nimport app.lawnchair.lawnfeed.PermissionActivity;\nimport app.lawnchair.lawnfeed.PermissionActivity.*;\nimport app.lawnchair.lawnfeed.R;\n\npublic class UpdateReceiver extends BroadcastReceiver {\n    @Override\n    public void onReceive(final Context context, final Intent intent) {\n        // Get our download link and setup receiver to install apk after download\n        final String link = intent.getStringExtra(\"downloadLink\");\n        final DownloadReceiver receiver = new DownloadReceiver() {\n            @Override\n            public void onDownloadDone(Uri uri) {\n                // Seems like Android has changed the way to open package manager on Nougat and higher\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n                    // Open package installer and install downloaded apk file\n                    Intent install = new Intent(Intent.ACTION_INSTALL_PACKAGE);\n                    install.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);\n\n                    // Pass downloaded file uri to our install intent\n                    Uri content = FileProvider.getUriForFile(context, context.getPackageName() + \".provider\", new File(uri.getPath()));\n                    install.setData(content);\n                    context.startActivity(install);\n                } else {\n                    // The old way before Nougat\n                    Intent install = new Intent(Intent.ACTION_VIEW);\n                    install.setDataAndType(uri, \"application/vnd.android.package-archive\");\n                    install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n                    context.startActivity(install);\n                }\n            }\n        };\n\n        // Request permissions and run asynchronously\n        PermissionActivity.callAsync(context, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PermissionActivity.REQUEST_CODE,\n            new PermissionResultCallback() {\n                @Override\n                public void onComplete(PermissionResponse response) {\n                    // Don't continue if permissing aren't granted\n                    if (!response.isGranted()) {\n                        Log.e(\"Updater\", \"No permissions granted!\");\n                        return;\n                    }\n\n                    String filename = intent.getStringExtra(\"filename\");\n\n                    // Check if our dir exists (theoretically it should, but you never know)\n                    File outputDir = new File(Environment.getExternalStorageDirectory(), \"Download\");\n                    if (!outputDir.exists()) {\n                        outputDir.mkdir();\n                    }\n\n                    File file = new File(outputDir, filename);\n\n                    // Call onDownloadDone if file already exists (not installed due to security settings, etc.)\n                    if (file.exists()) {\n                        receiver.onDownloadDone(Uri.parse(file.getAbsolutePath()));\n                        return;\n                    }\n\n                    // Start downloading\n                    Toast.makeText(context, R.string.downloading_toast, Toast.LENGTH_LONG).show();\n                    DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);\n\n                    if (link != null) {\n                        DownloadManager.Request request = new DownloadManager.Request(Uri.parse(link));\n                        request.setDestinationUri(Uri.fromFile(file));\n                        receiver.setDownloadId(downloadManager.enqueue(request));\n                    }\n\n                    // Register our download receiver\n                    receiver.setFilename(filename);\n                    context.getApplicationContext().registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));\n                }\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/app/lawnchair/lawnfeed/updater/Updater.java",
    "content": "package app.lawnchair.lawnfeed.updater;\n\nimport android.app.Activity;\nimport android.app.NotificationChannel;\nimport android.app.NotificationManager;\nimport android.app.PendingIntent;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.SharedPreferences;\nimport android.content.pm.PackageManager;\nimport android.media.RingtoneManager;\nimport android.net.ConnectivityManager;\nimport android.net.NetworkInfo;\nimport android.os.Build;\nimport android.util.Log;\n\nimport androidx.core.app.NotificationCompat;\n\nimport org.json.simple.JSONObject;\nimport org.json.simple.parser.JSONParser;\nimport org.json.simple.parser.ParseException;\n\nimport java.io.IOException;\nimport java.net.MalformedURLException;\nimport java.net.URL;\n\nimport app.lawnchair.lawnfeed.receivers.UpdateReceiver;\nimport app.lawnchair.lawnfeed.R;\n\npublic class Updater {\n    public static final String VERSION_URL = \"https://storage.codebucket.de/lawnchair/version.json\";\n\n    public static final String DOWNLOAD_URL = \"https://storage.codebucket.de/lawnchair/%1$s/Lawnfeed-%1$s.apk\";\n\n    private static final String PREFERENCES_NAME = \"updater\";\n\n    public static final String PREFERENCES_LAST_CHECKED = \"last_checked\";\n\n    public static final String PREFERENCES_CACHED_UPDATE = \"cached_update\";\n\n    private static final long TWELVE_HOURS = 43200000;\n\n    private static final String TAG = \"Updater\";\n\n    public static final String CHANNEL_ID = \"lawnfeed_updater\";\n\n    public static void checkUpdate(final Context context) {\n        final SharedPreferences prefs = context.getSharedPreferences(PREFERENCES_NAME, Activity.MODE_PRIVATE);\n\n        // Create notification channel on Android O\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,\n                    context.getResources().getString(R.string.lawnfeed_updates), NotificationManager.IMPORTANCE_DEFAULT);\n            context.getSystemService(NotificationManager.class).createNotificationChannel(channel);\n        }\n\n        UpdaterTask task = new UpdaterTask(context, VERSION_URL, new UpdateListener() {\n            @Override\n            public void onSuccess(Update update) {\n                // Don't notify the user if he is running newer version than the latest\n                if (getBuildNumber(context) >= update.getBuildNumber()) {\n                    Log.e(TAG, update.getBuildNumber() + \" is lower than \" + getBuildNumber(context) + \"?\");\n                    return;\n                }\n\n                // We need our url as String for the Intent\n                String url = update.getDownloadUrl().toString();\n\n                // Intent for download task\n                Intent intentAction = new Intent(context, UpdateReceiver.class);\n                intentAction.putExtra(\"downloadLink\", url);\n                intentAction.putExtra(\"filename\", url.substring(url.lastIndexOf('/') + 1, url.length()));\n\n                // Build notification\n                PendingIntent pendingIntent = PendingIntent.getBroadcast(context,0, intentAction, PendingIntent.FLAG_UPDATE_CURRENT);\n                NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)\n                        .setContentTitle(context.getResources().getString(R.string.update_available_title))\n                        .setContentText(context.getResources().getString(R.string.update_available))\n                        .setSmallIcon(R.drawable.ic_lawnchair)\n                        .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))\n                        .setVibrate(new long[]{0, 100, 100, 100})\n                        .setAutoCancel(true)\n                        .setContentIntent(pendingIntent);\n\n                NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);\n                notificationManager.notify(0, builder.build());\n\n                // Cache update\n                if (!update.isCached()) {\n                    prefs.edit()\n                            .putLong(PREFERENCES_LAST_CHECKED, System.currentTimeMillis())\n                            .putString(PREFERENCES_CACHED_UPDATE, update.toString())\n                            .apply();\n                }\n            }\n\n            @Override\n            public void onError(UpdateError error) {\n                Log.e(TAG, error.toString());\n            }\n        });\n\n        // Don't check for updates if last update check was not longer than 6 hours ago\n        long lastChecked = prefs.getLong(PREFERENCES_LAST_CHECKED, 0);\n        if (lastChecked + TWELVE_HOURS >= System.currentTimeMillis()) {\n            Log.i(TAG, \"Last update check was earlier than 12 hours ago, using cached info\");\n\n            task.onPostExecute(Update.fromString(prefs.getString(PREFERENCES_CACHED_UPDATE, \"\")));\n            return;\n        }\n\n        Log.i(TAG, \"Checking for new updates\");\n\n        // Run updater task in background\n        task.execute();\n    }\n\n    // Get current app build number from versionCode\n    public static int getBuildNumber(Context context) {\n        try {\n            return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;\n        } catch (PackageManager.NameNotFoundException ex) {}\n\n        return 0;\n    }\n\n    // Check if String is a valid URL\n    public static boolean isValidUrl(String url) {\n        try {\n            new URL(url);\n            return true;\n        } catch (MalformedURLException ignored) {}\n\n        return false;\n    }\n\n    // Check if device have a network connection\n    public static boolean isNetworkConnectivity(Context context) {\n        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);\n        if (cm != null) {\n            NetworkInfo networkInfo = cm.getActiveNetworkInfo();\n            if (networkInfo != null) {\n                return networkInfo.isConnected();\n            }\n        }\n\n        return false;\n    }\n\n    public static class Update {\n        private int buildNumber;\n        private URL download;\n\n        private boolean cached;\n\n        public Update(int buildNumber, URL download) {\n            this(buildNumber, download, false);\n        }\n\n        public Update(int buildNumber, URL download, boolean cached) {\n            this.buildNumber = buildNumber;\n            this.download = download;\n            this.cached = cached;\n        }\n\n        public Integer getBuildNumber() {\n            return buildNumber;\n        }\n\n        public URL getDownloadUrl() {\n            return download;\n        }\n\n        public boolean isCached() {\n            return cached;\n        }\n\n        @Override\n        public String toString() {\n            // Create a new json object\n            JSONObject obj = new JSONObject();\n            obj.put(\"buildNumber\", buildNumber);\n            obj.put(\"download\", download.toString());\n\n            // Return parsed json to string\n            return obj.toJSONString();\n        }\n\n        public static Update fromString(String json) {\n            try {\n                // Read json string\n                JSONObject obj = (JSONObject) new JSONParser().parse(json);\n                int buildNumber = ((Long) obj.get(\"buildNumber\")).intValue();\n                URL download = new URL((String) obj.get(\"download\"));\n\n                // Return cached update from json\n                return new Update(buildNumber, download, true);\n            } catch (IOException | ParseException ex) {\n                Log.e(TAG, \"Invalid JSON object: \" + json);\n            }\n\n            // Shouldn't be returned, but it may happen\n            return new Update(0, null, true);\n        }\n    }\n\n    public enum UpdateError {\n        // No internet connection available\n        NETWORK_NOT_AVAILABLE,\n\n        // URL for version info is not valid\n        VERSION_URL_MALFORMED,\n\n        // Version info is invalid or unreachable\n        VERSION_ERROR,\n\n        // Download URL for update is not valid\n        INVALID_DOWNLOAD_URL\n    }\n\n    public interface UpdateListener {\n        void onSuccess(Update update);\n\n        void onError(UpdateError error);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/app/lawnchair/lawnfeed/updater/UpdaterTask.java",
    "content": "package app.lawnchair.lawnfeed.updater;\n\nimport android.content.Context;\nimport android.os.AsyncTask;\n\nimport org.json.simple.JSONObject;\nimport org.json.simple.parser.JSONParser;\nimport org.json.simple.parser.ParseException;\n\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.net.HttpURLConnection;\nimport java.net.MalformedURLException;\nimport java.net.URL;\n\npublic class UpdaterTask extends AsyncTask<Void, Void, Updater.Update> {\n    private Context context;\n    private String update;\n    private Updater.UpdateListener listener;\n\n    public UpdaterTask(Context context, String update, Updater.UpdateListener listener) {\n        this.context = context;\n        this.update = update;\n        this.listener = listener;\n    }\n\n    @Override\n    protected void onPreExecute() {\n        super.onPreExecute();\n\n        // No listener = no actions\n        if (listener == null) {\n            cancel(true);\n            return;\n        }\n\n        // Check if device is connected to the Internet\n        if (!Updater.isNetworkConnectivity(context)) {\n            listener.onError(Updater.UpdateError.NETWORK_NOT_AVAILABLE);\n            cancel(true);\n            return;\n        }\n\n        // Check if URL is valid\n        if (!Updater.isValidUrl(update)) {\n            listener.onError(Updater.UpdateError.VERSION_URL_MALFORMED);\n            cancel(true);\n            return;\n        }\n    }\n\n    @Override\n    protected Updater.Update doInBackground(Void... voids) {\n        // Our version.json from the storage server\n        JSONObject json = null;\n\n        try {\n            // Retrieve json object from URL\n            URL url = new URL(update);\n            HttpURLConnection connection = (HttpURLConnection) url.openConnection();\n            json = (JSONObject) new JSONParser().parse(new InputStreamReader(connection.getInputStream()));\n        } catch (IOException | ParseException ex) {\n            // Throw error if listener is not null\n            if (listener != null) {\n                listener.onError(Updater.UpdateError.VERSION_ERROR);\n            }\n\n            // Of course cancel the task\n            cancel(true);\n            return null;\n        }\n\n        // Don't continue if the json doesn't contain the last build number\n        if (!json.containsKey(\"travis_build_number\")) {\n            return null;\n        }\n\n        int buildNumber = Integer.valueOf((String) json.get(\"travis_build_number\"));\n\n        String version = (String) json.get(\"app_version\");\n        String downloadUrl = String.format(Updater.DOWNLOAD_URL, version);\n\n        URL download = null;\n\n        try {\n            download = new URL(downloadUrl);\n        } catch (MalformedURLException ex) {\n            // Throw error if listener is not null\n            if (listener != null) {\n                listener.onError(Updater.UpdateError.INVALID_DOWNLOAD_URL);\n            }\n\n            // Of course cancel the task\n            cancel(true);\n            return null;\n        }\n\n        return new Updater.Update(buildNumber, download);\n    }\n\n    @Override\n    protected void onPostExecute(Updater.Update update) {\n        super.onPostExecute(update);\n\n        // Return fetched update to listener\n        if (listener != null) {\n            listener.onSuccess(update);\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_lawnchair.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"512.000000dp\"\n    android:height=\"512.000000dp\"\n    android:viewportWidth=\"512.000000\"\n    android:viewportHeight=\"512.000000\">\n\n    <path\n        android:fillColor=\"#ffffff\"\n        android:pathData=\"M37.5 25.9c-7.4 2.3-14.8 10-17.1 17.7-2.2 7.5-2.1 10.5 0.6 17.8 2.1 5.6 10.5 14.1\n209.4 213 114 113.9 208.6 208.1 210.3 209.3 5.8 4.2 16.5 5.5 24.5 3.1 4.8-1.4\n13.2-8.7 15.3-13.1 2.1-4.7 3.3-10.8 2.8-15.3-1.1-10.4 8.1-0.8-212.3-221.2C107.4\n73.6 62.6 29.3 58.6 27.3c-6.1-3.1-13.9-3.6-21.1-1.4zM108.2 360.3c-78.5 78.6-84.5\n84.8-86.7 90.2-1.3 3.3-2.1 6.3-1.9 6.7 0.3 0.4 0.1 0.8-0.4 0.8 -1.1 0-0.8 3.9 0.9 10 3.4\n12.4 13.8 20.1 26.9 19.9 5.2-0.1 17.7-4 83.8-26.1 42.7-14.2 82.4-27.4 88.2-29.3\n20.7-6.6 97.2-32.4 97.7-32.9 0.3 -0.2-9.2-10.1-21-22l-21.6-21.5-35.8 12c-19.7\n6.6-36.5 12.3-37.3 12.6-0.8 0.3 -1.9 0.7 -2.5 0.9 -0.5 0.1 -11.8 3.9-25 8.3-13.2\n4.5-24.2 8.1-24.4 8.1-0.3 0 18.1-18.7\n40.9-41.6l41.9-41.9c0.4-0.5-37.7-38.5-38.7-38.5-0.4 0-38.6 38-85 84.3zM450.5\n297.7c-3.3 0.8 -58.5 19.2-59.6 19.9-0.6 0.3 8.7 10.3 20.6 22.1l21.5 21.6\n17.3-5.8c9.5-3.2 19.1-7.1 21.5-8.7 17-11.6\n14.4-38.7-4.6-47-4.4-2-12.9-3-16.7-2.1z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@mipmap/ic_launcher_background\"/>\n    <foreground android:drawable=\"@mipmap/ic_launcher_foreground\"/>\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@mipmap/ic_launcher_background\"/>\n    <foreground android:drawable=\"@mipmap/ic_launcher_foreground\"/>\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"colorPrimaryDark\">#303F9F</color>\n    <color name=\"colorAccent\">#FF4081</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">Lawnfeed</string>\n    <string name=\"update_available_title\">Update available</string>\n    <string name=\"update_available\">A new version of Lawnfeed is available to download!</string>\n    <string name=\"downloading_toast\">Downloading...</string>\n    <string name=\"download_file_error\">Error downloading file</string>\n    <string name=\"lawnfeed_updates\">Lawnfeed Updates</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"android:Theme.Material.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"android:colorPrimary\">@color/colorPrimary</item>\n        <item name=\"android:colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"android:colorAccent\">@color/colorAccent</item>\n    </style>\n\n    <!-- Transparent theme for PermissionActivity -->\n    <style name=\"Theme.Transparent\" parent=\"android:Theme\">\n        <item name=\"android:windowIsTranslucent\">true</item>\n        <item name=\"android:windowBackground\">@android:color/transparent</item>\n        <item name=\"android:windowContentOverlay\">@null</item>\n        <item name=\"android:windowNoTitle\">true</item>\n        <item name=\"android:windowIsFloating\">true</item>\n        <item name=\"android:backgroundDimEnabled\">false</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/xml/file_paths.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths>\n    <external-path name=\"external_files\" path=\".\"/>\n</paths>"
  },
  {
    "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.2.30'\n\n    repositories {\n        google()\n        jcenter()\n    }\n\n    dependencies {\n        classpath 'com.android.tools.build:gradle:3.1.3'\n        classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version\"\n    }\n}\n\nallprojects {\n    repositories {\n        google()\n        jcenter()\n        maven { url 'https://jitpack.io' }\n    }\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Sat Mar 27 07:42:52 CET 2021\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-4.8-all.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx1536m\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\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": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windows variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "settings.gradle",
    "content": "include ':app', ':launcherclient'\n"
  }
]