Repository: LawnchairLauncher/lawnfeed
Branch: master
Commit: 39ab76bad5d7
Files: 34
Total size: 57.5 KB
Directory structure:
gitextract_4wvmvqnb/
├── .github/
│ └── workflows/
│ └── main.yml
├── .gitignore
├── .gitmodules
├── app/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── aidl/
│ │ └── amirz/
│ │ └── aidlbridge/
│ │ ├── IBridge.aidl
│ │ └── IBridgeCallback.aidl
│ ├── java/
│ │ ├── amirz/
│ │ │ └── aidlbridge/
│ │ │ ├── BridgeImpl.java
│ │ │ ├── BridgeService.java
│ │ │ └── TransactProxy.java
│ │ └── app/
│ │ └── lawnchair/
│ │ └── lawnfeed/
│ │ ├── LauncherClientProxyService.kt
│ │ ├── PermissionActivity.java
│ │ ├── ProxyImpl.kt
│ │ ├── bridge/
│ │ │ └── TransactProxy.kt
│ │ ├── receivers/
│ │ │ ├── DownloadReceiver.java
│ │ │ └── UpdateReceiver.java
│ │ └── updater/
│ │ ├── Updater.java
│ │ └── UpdaterTask.java
│ └── res/
│ ├── drawable/
│ │ └── ic_lawnchair.xml
│ ├── mipmap-anydpi-v26/
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ ├── values/
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── xml/
│ └── file_paths.xml
├── build.gradle
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/main.yml
================================================
name: Build signed debug APK
on:
workflow_dispatch:
push:
branches:
- master
jobs:
build-signed-debug-apk:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- name: Check out repository
uses: actions/checkout@v2.3.4
with:
submodules: true
- name: Restore Gradle cache
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: ${{ runner.os }}-gradle-
- name: Set up Java 8
uses: actions/setup-java@v1.4.3
with:
java-version: 8
- name: Grant execution permission to Gradle Wrapper
run: chmod +x gradlew
- name: Build debug APK
run: ./gradlew assembleDebug
- name: Setup build tool version variable
shell: bash
run: |
BUILD_TOOL_VERSION=$(ls /usr/local/lib/android/sdk/build-tools/ | tail -n 1)
echo "BUILD_TOOL_VERSION=$BUILD_TOOL_VERSION" >> $GITHUB_ENV
echo Last build tool version is: $BUILD_TOOL_VERSION
- name: Sign debug APK
uses: r0adkll/sign-android-release@v1
id: sign-debug-apk
with:
releaseDirectory: app/build/outputs/apk/debug
signingKeyBase64: ${{ secrets.KEYSTORE }}
alias: ${{ secrets.KEY_ALIAS }}
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: ${{ env.BUILD_TOOL_VERSION }}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: Signed Debug APK
path: ${{ steps.sign-debug-apk.outputs.signedReleaseFile }}
================================================
FILE: .gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.externalNativeBuild
.idea
================================================
FILE: .gitmodules
================================================
[submodule "launcherclient"]
path = launcherclient
url = https://github.com/LawnchairLauncher/launcherclient.git
================================================
FILE: app/.gitignore
================================================
/build
================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
final def commitHash = { ->
final def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-parse', '--short=7', 'HEAD'
standardOutput = stdout
}
stdout.toString().trim()
}
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "app.lawnchair.lawnfeed"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "4.0+${commitHash()}"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
applicationVariants.all { variant ->
variant.outputs.all {
outputFileName = "Lawnfeed ${variant.versionName}.apk"
}
}
buildTypes {
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "androidx.core:core:1.0.2"
implementation('com.googlecode.json-simple:json-simple:1.1.1') {
exclude group: 'org.hamcrest', module: 'hamcrest-core'
}
implementation project(':launcherclient')
}
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\Users\papho\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
================================================
FILE: app/src/main/aidl/amirz/aidlbridge/IBridge.aidl
================================================
package amirz.aidlbridge;
import amirz.aidlbridge.IBridgeCallback;
interface IBridge {
oneway void bindService(in IBridgeCallback cb, in int flags);
}
================================================
FILE: app/src/main/aidl/amirz/aidlbridge/IBridgeCallback.aidl
================================================
package amirz.aidlbridge;
interface IBridgeCallback {
oneway void onServiceConnected(in ComponentName name, in IBinder service);
oneway void onServiceDisconnected(in ComponentName name);
}
================================================
FILE: app/src/main/java/amirz/aidlbridge/BridgeImpl.java
================================================
package amirz.aidlbridge;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.net.Uri;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import java.util.HashSet;
import java.util.Set;
import app.lawnchair.lawnfeed.bridge.TransactProxy;
class BridgeImpl extends IBridge.Stub {
private static final String TAG = "BridgeImpl";
private final Context mContext;
private final String mPackage;
private final Intent mIntent;
private final Set mConnections = new HashSet<>();
BridgeImpl(Context context, Intent intent) {
mContext = context;
Uri caller = intent.getData();
String authority = caller.getEncodedAuthority();
mPackage = TextUtils.isEmpty(authority) ? "" : authority.split(":")[0];
mIntent = intent.cloneFilter();
mIntent.setPackage("com.google.android.googlequicksearchbox");
String auth = context.getPackageName() + ":" + Process.myUid();
mIntent.setData(caller.buildUpon().encodedAuthority(auth).build());
}
String getPackage() {
return mPackage;
}
@Override
public void bindService(final IBridgeCallback cb, int flags) {
Log.e(TAG, "Connect request from " + getPackage());
ServiceConnection connection = new ServiceConnection() {
private boolean mConnected;
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (!mConnected) {
mConnected = true;
Log.e(TAG, "Connected for " + mPackage);
try {
cb.onServiceConnected(name, new TransactProxy(service, mContext));
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
if (mConnected) {
mConnected = false;
Log.e(TAG, "Disconnected for " + mPackage);
try {
cb.onServiceDisconnected(name);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
};
if (mContext.bindService(mIntent, connection, flags)) {
mConnections.add(connection);
}
}
void disconnect() {
Log.e(TAG, "Disconnect " + mPackage + " with " + mConnections.size() + " connections");
for (ServiceConnection connection : mConnections) {
mContext.unbindService(connection);
connection.onServiceDisconnected(null);
}
mConnections.clear();
}
}
================================================
FILE: app/src/main/java/amirz/aidlbridge/BridgeService.java
================================================
package amirz.aidlbridge;
import android.app.Service;
import android.content.Intent;
import android.net.Uri;
import android.os.IBinder;
import android.util.Log;
import java.util.HashMap;
import java.util.Map;
public class BridgeService extends Service {
private static final String TAG = "BridgeService";
private static String sLastConnection;
private final Map mBridges = new HashMap<>();
@Override
public IBinder onBind(Intent intent) {
Uri caller = intent.getData();
if (caller != null) {
Log.e(TAG, "Bind from " + caller.toString());
if (!mBridges.containsKey(intent)) {
mBridges.put(intent, new BridgeImpl(getApplicationContext(), intent) {
@Override
public void bindService(final IBridgeCallback cb, int flags) {
sLastConnection = getPackage();
for (BridgeImpl bridge : mBridges.values()) {
if (bridge != this) {
bridge.disconnect();
}
}
super.bindService(cb, flags);
}
});
}
return mBridges.get(intent);
}
return null;
}
@Override
public boolean onUnbind(Intent intent) {
Uri caller = intent.getData();
if (caller != null) {
Log.e(TAG, "Unbind from " + caller.toString());
if (mBridges.containsKey(intent)) {
mBridges.remove(intent).disconnect();
}
}
return super.onUnbind(intent);
}
public static String getLastConnection() {
return sLastConnection;
}
}
================================================
FILE: app/src/main/java/amirz/aidlbridge/TransactProxy.java
================================================
package amirz.aidlbridge;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
class TransactProxy extends Binder {
private final IBinder mTarget;
TransactProxy(IBinder target) {
mTarget = target;
}
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
return mTarget.transact(code, data, reply, flags);
}
}
================================================
FILE: app/src/main/java/app/lawnchair/lawnfeed/LauncherClientProxyService.kt
================================================
package app.lawnchair.lawnfeed
import android.app.Service
import android.content.Intent
import android.os.IBinder
class LauncherClientProxyService : Service() {
override fun onBind(intent: Intent): IBinder? {
return getBinder()
}
override fun onUnbind(intent: Intent?): Boolean {
binder?.onUnbind()
binder = null
stopSelf()
return super.onUnbind(intent)
}
private fun getBinder(): ProxyImpl {
if (binder == null) {
binder = ProxyImpl(applicationContext)
}
return binder!!
}
private var binder: ProxyImpl? = null
}
================================================
FILE: app/src/main/java/app/lawnchair/lawnfeed/PermissionActivity.java
================================================
package app.lawnchair.lawnfeed;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.ResultReceiver;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
public class PermissionActivity extends Activity {
public static final int REQUEST_CODE = 1337;
private ResultReceiver resultReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Don't continue if the activity doesn't have an intent
if (getIntent() == null) {
finish();
return;
}
// Get permissions array which we want to request
resultReceiver = getIntent().getParcelableExtra("resultReceiver");
String[] permissionsArray = getIntent().getStringArrayExtra("permissions");
int requestCode = getIntent().getIntExtra("requestCode", REQUEST_CODE);
// Check if those permissions are already granted
if (PermissionResponse.hasPermissions(this, permissionsArray)) {
// Proceed like those permissions have been now granted
onComplete(requestCode, permissionsArray, new int[]{ PackageManager.PERMISSION_GRANTED });
} else {
// Otherwise request those permissions and wait for users response
ActivityCompat.requestPermissions(this, permissionsArray, requestCode);
}
}
private void onComplete(int requestCode, String[] permissions, int[] grantResult) {
Bundle bundle = new Bundle();
bundle.putStringArray("permissions", permissions);
bundle.putIntArray("grantResult", grantResult);
bundle.putInt("requestCode", requestCode);
// Send our callback to the result receiver
resultReceiver.send(requestCode, bundle);
finish();
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResult) {
super.onRequestPermissionsResult(requestCode, permissions, grantResult);
onComplete(requestCode, permissions, grantResult);
}
public static void callAsync(Context context, String[] permissions, int requestCode, final PermissionResultCallback callback) {
// Proceed to the callback if permissions were already granted
if (PermissionResponse.hasPermissions(context, permissions)) {
callback.onComplete(new PermissionResponse(permissions, new int[]{PackageManager.PERMISSION_GRANTED}, requestCode));
return;
}
// Our result receiver to get the response asynchronously
ResultReceiver receiver = new ResultReceiver(new Handler(Looper.getMainLooper())) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
super.onReceiveResult(resultCode, resultData);
int[] grantResult = resultData.getIntArray("grantResult");
String[] permissions = resultData.getStringArray("permissions");
// Call the callback with the result
callback.onComplete(new PermissionResponse(permissions, grantResult, resultCode));
}
};
// Build our intent to launch
Intent intent = new Intent(context, PermissionActivity.class);
intent.putExtra("requestCode", requestCode);
intent.putExtra("permissions", permissions);
intent.putExtra("resultReceiver", receiver);
intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
// Start our activity and wait
context.startActivity(intent);
}
// Contains results from requesting the permissions
public static class PermissionResponse {
private String[] permissions;
private int [] grantResult;
private int requestCode;
public PermissionResponse(String[] permissions, int[] grantResult, int requestCode) {
this.permissions = permissions;
this.grantResult = grantResult;
this.requestCode = requestCode;
}
public boolean isGranted() {
return (grantResult != null && grantResult.length > 0 && grantResult[0] == PackageManager.PERMISSION_GRANTED);
}
public String[] getPermissions() {
return permissions;
}
public int[] getGrantResult() {
return grantResult;
}
public int getRequestCode() {
return requestCode;
}
public static boolean hasPermissions(Context context, String[] permissionsArray) {
// If a permission isn't granted => return false
for (String permission : permissionsArray) {
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED){
return false;
}
}
// Return true only when all permissions are granted
return true;
}
}
// Simple interface to handle our async callbacks
public interface PermissionResultCallback {
void onComplete(PermissionResponse response);
}
}
================================================
FILE: app/src/main/java/app/lawnchair/lawnfeed/ProxyImpl.kt
================================================
package app.lawnchair.lawnfeed
import android.content.*
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Binder
import android.os.Bundle
import android.os.IBinder
import android.os.Process
import android.util.Log
import app.lawnchair.launcherclient.ILauncherClientProxy
import app.lawnchair.launcherclient.ILauncherClientProxyCallback
import app.lawnchair.launcherclient.LauncherClientProxyConnection
import app.lawnchair.launcherclient.WindowLayoutParams
import app.lawnchair.lawnfeed.bridge.TransactProxy
import app.lawnchair.lawnfeed.updater.Updater
import com.google.android.libraries.launcherclient.ILauncherOverlay
import com.google.android.libraries.launcherclient.ILauncherOverlayCallback
class ProxyImpl(val context: Context) : ILauncherClientProxy.Stub() {
private lateinit var proxyCallback: ILauncherClientProxyCallback
private var overlayCallbacks = OverlayCallbacks()
private var destroyed = false
private var serviceConnected: Boolean = false
private var overlay: ILauncherOverlay? = null
private var allowed = false
private val serviceConnection = OverlayServiceConnection()
private val serviceIntent = ProxyImpl.getServiceIntent(context)
private var serviceStatus: Int = 0
override fun reconnect(): Int {
enforcePermission()
try {
if (destroyed || serviceStatus != 0)
return serviceStatus
if (sApplicationConnection != null && sApplicationConnection?.packageName != serviceIntent.`package`)
context.unbindService(sApplicationConnection)
if (sApplicationConnection == null) {
sApplicationConnection = AppServiceConnection(serviceIntent.`package`)
if (!connectSafely(context, sApplicationConnection!!, Context.BIND_WAIVE_PRIORITY)) {
sApplicationConnection = null
}
}
if (sApplicationConnection != null) {
serviceStatus = LauncherClientProxyConnection.SERVICE_CONNECTING
if (!connectSafely(context, serviceConnection, Context.BIND_ADJUST_WITH_ACTIVITY)) {
serviceStatus = LauncherClientProxyConnection.SERVICE_DISCONNECTED
} else {
serviceConnected = true
}
}
if (serviceStatus == 0) {
proxyCallback.overlayStatusChanged(0)
}
} catch (e: Exception) {
Log.d(TAG, "error reconnecting", e)
}
return serviceStatus
}
private fun connectSafely(context: Context, conn: ServiceConnection, flags: Int): Boolean {
try {
return context.bindService(serviceIntent, conn, flags or Context.BIND_AUTO_CREATE)
} catch (e: SecurityException) {
Log.e("DrawerOverlayClient", "Unable to connect to overlay service")
return false
}
}
override fun closeOverlay(options: Int) {
enforcePermission()
overlay?.closeOverlay(options)
}
override fun endScroll() {
enforcePermission()
overlay?.endScroll()
}
override fun onPause() {
enforcePermission()
overlay?.onPause()
}
override fun onResume() {
enforcePermission()
overlay?.onResume()
}
override fun onScroll(progress: Float) {
enforcePermission()
overlay?.onScroll(progress)
}
override fun openOverlay(options: Int) {
enforcePermission()
overlay?.openOverlay(options)
}
override fun startScroll() {
enforcePermission()
overlay?.startScroll()
}
override fun windowAttached(attrs: WindowLayoutParams, options: Int) {
enforcePermission()
overlay?.windowAttached(attrs.layoutParams, overlayCallbacks, options)
}
override fun windowAttached2(bundle: Bundle) {
enforcePermission()
overlay?.windowAttached2(bundle, overlayCallbacks)
}
override fun setActivityState(activityState: Int) {
enforcePermission()
overlay?.setActivityState(activityState)
}
override fun windowDetached(isChangingConfigurations: Boolean) {
enforcePermission()
overlay?.windowDetached(isChangingConfigurations)
}
override fun onQsbClick(intent: Intent?) {
enforcePermission()
context.sendOrderedBroadcast(intent, null, object : BroadcastReceiver() {
@Suppress("NAME_SHADOWING")
override fun onReceive(context: Context?, intent: Intent?) {
proxyCallback.onQsbResult(resultCode)
}
}, null, 0, null, null)
}
override fun requestVoiceDetection(start: Boolean) {
enforcePermission()
overlay?.requestVoiceDetection(start)
}
override fun hasOverlayContent(): Boolean {
enforcePermission()
return overlay?.hasOverlayContent() ?: false
}
override fun startSearch(data: ByteArray?, bundle: Bundle?): Boolean {
enforcePermission()
return overlay?.startSearch(data, bundle) ?: false
}
override fun isVoiceDetectionRunning(): Boolean {
enforcePermission()
return overlay?.isVoiceDetectionRunning ?: false
}
override fun getVoiceSearchLanguage(): String {
enforcePermission()
return overlay?.voiceSearchLanguage ?: ""
}
override fun init(callback: ILauncherClientProxyCallback): Int {
allowed = callingPackage in TransactProxy.allowedPackages
enforcePermission()
proxyCallback = callback
Updater.checkUpdate(context)
ProxyImpl.getVersion(context)
return version
}
private val callingPackage get() = context.packageManager.getNameForUid(Binder.getCallingUid())
fun enforcePermission() {
if (!allowed)
throw SecurityException("$callingPackage is not allowed to call this service")
}
inner class OverlayCallbacks : ILauncherOverlayCallback.Stub() {
override fun overlayScrollChanged(progress: Float) {
if (!destroyed)
proxyCallback.overlayScrollChanged(progress)
}
override fun overlayStatusChanged(status: Int) {
if (!destroyed)
proxyCallback.overlayStatusChanged(status)
}
}
internal inner class AppServiceConnection(val packageName: String) : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
}
override fun onServiceDisconnected(name: ComponentName) {
if (name.packageName == packageName) {
sApplicationConnection = null
}
}
}
private inner class OverlayServiceConnection : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
overlay = ILauncherOverlay.Stub.asInterface(service)
serviceStatus = LauncherClientProxyConnection.SERVICE_CONNECTED
proxyCallback.onServiceConnected()
}
override fun onServiceDisconnected(name: ComponentName) {
overlay = null
serviceStatus = LauncherClientProxyConnection.SERVICE_DISCONNECTED
proxyCallback.onServiceDisconnected()
}
}
companion object {
const val TAG = "ProxyImpl"
private var sApplicationConnection: AppServiceConnection? = null
private var version = -1
internal fun getServiceIntent(context: Context): Intent {
val uri = Uri.parse("app://${context.packageName}:${Process.myUid()}").buildUpon()
.appendQueryParameter("v", Integer.toString(5))
.build()
return Intent("com.android.launcher3.WINDOW_OVERLAY")
.setPackage("com.google.android.googlequicksearchbox")
.setData(uri)
}
private fun getVersion(context: Context) {
val resolveService = context.packageManager.resolveService(getServiceIntent(context), PackageManager.GET_META_DATA)
version = resolveService?.serviceInfo?.metaData?.getInt("service.api.version", 1) ?: 1
Log.v("LauncherClient", "version: $version")
}
}
fun onUnbind() {
Log.d(TAG, "onUnbind")
destroyed = true
if (serviceConnected)
context.unbindService(serviceConnection)
if (sApplicationConnection != null)
context.unbindService(sApplicationConnection)
sApplicationConnection = null
}
}
================================================
FILE: app/src/main/java/app/lawnchair/lawnfeed/bridge/TransactProxy.kt
================================================
package app.lawnchair.lawnfeed.bridge
import android.content.Context
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.os.Binder
import android.os.IBinder
import android.os.Parcel
import app.lawnchair.lawnfeed.Manifest
class TransactProxy(private val target: IBinder, private val context: Context) : Binder() {
private val permissionGranted by lazy {
context.checkCallingPermission(Manifest.permission.CONNECT_SERVICE) == PERMISSION_GRANTED }
private val allowed by lazy { permissionGranted || callingPackage in allowedPackages }
private val callingPackage get() = context.packageManager.getNameForUid(getCallingUid())
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
enforcePermission()
return target.transact(code, data, reply, flags)
}
private fun enforcePermission() {
if (!allowed)
throw SecurityException("$callingPackage is not allowed to call this service")
}
companion object {
@JvmStatic
val allowedPackages = setOf(
"app.lawnchair",
"app.lawnchair.play",
"app.lawnchair.nightly",
"ch.deletescape.lawnchair.plah",
"ch.deletescape.lawnchair"
)
}
}
================================================
FILE: app/src/main/java/app/lawnchair/lawnfeed/receivers/DownloadReceiver.java
================================================
package app.lawnchair.lawnfeed.receivers;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.widget.Toast;
import app.lawnchair.lawnfeed.R;
public abstract class DownloadReceiver extends BroadcastReceiver {
public String mFilename;
private long downloadId;
@Override
public void onReceive(Context context, Intent intent) {
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
String action = intent.getAction();
// We only want to check if the download has completed
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
// Check if file has been successfully downloaded
Cursor c = downloadManager.query(query);
if (c.moveToFirst()) {
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
int status = c.getInt(columnIndex);
switch (status) {
// If everything is fine, call the abstract method and proceed
case DownloadManager.STATUS_SUCCESSFUL:
Uri uri = Uri.parse(c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)));
onDownloadDone(uri);
break;
// Otherwise tell the user that the download failed
default:
Toast.makeText(context, R.string.download_file_error, Toast.LENGTH_LONG);
break;
}
context.unregisterReceiver(this);
}
// Close any open resources
c.close();
}
}
public void setDownloadId(long id) {
this.downloadId = id;
}
public void setFilename(String filename) {
this.mFilename = filename;
}
public abstract void onDownloadDone(Uri uri);
}
================================================
FILE: app/src/main/java/app/lawnchair/lawnfeed/receivers/UpdateReceiver.java
================================================
package app.lawnchair.lawnfeed.receivers;
import android.Manifest;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;
import androidx.core.content.FileProvider;
import java.io.File;
import app.lawnchair.lawnfeed.PermissionActivity;
import app.lawnchair.lawnfeed.PermissionActivity.*;
import app.lawnchair.lawnfeed.R;
public class UpdateReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
// Get our download link and setup receiver to install apk after download
final String link = intent.getStringExtra("downloadLink");
final DownloadReceiver receiver = new DownloadReceiver() {
@Override
public void onDownloadDone(Uri uri) {
// Seems like Android has changed the way to open package manager on Nougat and higher
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// Open package installer and install downloaded apk file
Intent install = new Intent(Intent.ACTION_INSTALL_PACKAGE);
install.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// Pass downloaded file uri to our install intent
Uri content = FileProvider.getUriForFile(context, context.getPackageName() + ".provider", new File(uri.getPath()));
install.setData(content);
context.startActivity(install);
} else {
// The old way before Nougat
Intent install = new Intent(Intent.ACTION_VIEW);
install.setDataAndType(uri, "application/vnd.android.package-archive");
install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(install);
}
}
};
// Request permissions and run asynchronously
PermissionActivity.callAsync(context, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PermissionActivity.REQUEST_CODE,
new PermissionResultCallback() {
@Override
public void onComplete(PermissionResponse response) {
// Don't continue if permissing aren't granted
if (!response.isGranted()) {
Log.e("Updater", "No permissions granted!");
return;
}
String filename = intent.getStringExtra("filename");
// Check if our dir exists (theoretically it should, but you never know)
File outputDir = new File(Environment.getExternalStorageDirectory(), "Download");
if (!outputDir.exists()) {
outputDir.mkdir();
}
File file = new File(outputDir, filename);
// Call onDownloadDone if file already exists (not installed due to security settings, etc.)
if (file.exists()) {
receiver.onDownloadDone(Uri.parse(file.getAbsolutePath()));
return;
}
// Start downloading
Toast.makeText(context, R.string.downloading_toast, Toast.LENGTH_LONG).show();
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
if (link != null) {
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(link));
request.setDestinationUri(Uri.fromFile(file));
receiver.setDownloadId(downloadManager.enqueue(request));
}
// Register our download receiver
receiver.setFilename(filename);
context.getApplicationContext().registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}
}
);
}
}
================================================
FILE: app/src/main/java/app/lawnchair/lawnfeed/updater/Updater.java
================================================
package app.lawnchair.lawnfeed.updater;
import android.app.Activity;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.media.RingtoneManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import app.lawnchair.lawnfeed.receivers.UpdateReceiver;
import app.lawnchair.lawnfeed.R;
public class Updater {
public static final String VERSION_URL = "https://storage.codebucket.de/lawnchair/version.json";
public static final String DOWNLOAD_URL = "https://storage.codebucket.de/lawnchair/%1$s/Lawnfeed-%1$s.apk";
private static final String PREFERENCES_NAME = "updater";
public static final String PREFERENCES_LAST_CHECKED = "last_checked";
public static final String PREFERENCES_CACHED_UPDATE = "cached_update";
private static final long TWELVE_HOURS = 43200000;
private static final String TAG = "Updater";
public static final String CHANNEL_ID = "lawnfeed_updater";
public static void checkUpdate(final Context context) {
final SharedPreferences prefs = context.getSharedPreferences(PREFERENCES_NAME, Activity.MODE_PRIVATE);
// Create notification channel on Android O
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
context.getResources().getString(R.string.lawnfeed_updates), NotificationManager.IMPORTANCE_DEFAULT);
context.getSystemService(NotificationManager.class).createNotificationChannel(channel);
}
UpdaterTask task = new UpdaterTask(context, VERSION_URL, new UpdateListener() {
@Override
public void onSuccess(Update update) {
// Don't notify the user if he is running newer version than the latest
if (getBuildNumber(context) >= update.getBuildNumber()) {
Log.e(TAG, update.getBuildNumber() + " is lower than " + getBuildNumber(context) + "?");
return;
}
// We need our url as String for the Intent
String url = update.getDownloadUrl().toString();
// Intent for download task
Intent intentAction = new Intent(context, UpdateReceiver.class);
intentAction.putExtra("downloadLink", url);
intentAction.putExtra("filename", url.substring(url.lastIndexOf('/') + 1, url.length()));
// Build notification
PendingIntent pendingIntent = PendingIntent.getBroadcast(context,0, intentAction, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
.setContentTitle(context.getResources().getString(R.string.update_available_title))
.setContentText(context.getResources().getString(R.string.update_available))
.setSmallIcon(R.drawable.ic_lawnchair)
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
.setVibrate(new long[]{0, 100, 100, 100})
.setAutoCancel(true)
.setContentIntent(pendingIntent);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0, builder.build());
// Cache update
if (!update.isCached()) {
prefs.edit()
.putLong(PREFERENCES_LAST_CHECKED, System.currentTimeMillis())
.putString(PREFERENCES_CACHED_UPDATE, update.toString())
.apply();
}
}
@Override
public void onError(UpdateError error) {
Log.e(TAG, error.toString());
}
});
// Don't check for updates if last update check was not longer than 6 hours ago
long lastChecked = prefs.getLong(PREFERENCES_LAST_CHECKED, 0);
if (lastChecked + TWELVE_HOURS >= System.currentTimeMillis()) {
Log.i(TAG, "Last update check was earlier than 12 hours ago, using cached info");
task.onPostExecute(Update.fromString(prefs.getString(PREFERENCES_CACHED_UPDATE, "")));
return;
}
Log.i(TAG, "Checking for new updates");
// Run updater task in background
task.execute();
}
// Get current app build number from versionCode
public static int getBuildNumber(Context context) {
try {
return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;
} catch (PackageManager.NameNotFoundException ex) {}
return 0;
}
// Check if String is a valid URL
public static boolean isValidUrl(String url) {
try {
new URL(url);
return true;
} catch (MalformedURLException ignored) {}
return false;
}
// Check if device have a network connection
public static boolean isNetworkConnectivity(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm != null) {
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
if (networkInfo != null) {
return networkInfo.isConnected();
}
}
return false;
}
public static class Update {
private int buildNumber;
private URL download;
private boolean cached;
public Update(int buildNumber, URL download) {
this(buildNumber, download, false);
}
public Update(int buildNumber, URL download, boolean cached) {
this.buildNumber = buildNumber;
this.download = download;
this.cached = cached;
}
public Integer getBuildNumber() {
return buildNumber;
}
public URL getDownloadUrl() {
return download;
}
public boolean isCached() {
return cached;
}
@Override
public String toString() {
// Create a new json object
JSONObject obj = new JSONObject();
obj.put("buildNumber", buildNumber);
obj.put("download", download.toString());
// Return parsed json to string
return obj.toJSONString();
}
public static Update fromString(String json) {
try {
// Read json string
JSONObject obj = (JSONObject) new JSONParser().parse(json);
int buildNumber = ((Long) obj.get("buildNumber")).intValue();
URL download = new URL((String) obj.get("download"));
// Return cached update from json
return new Update(buildNumber, download, true);
} catch (IOException | ParseException ex) {
Log.e(TAG, "Invalid JSON object: " + json);
}
// Shouldn't be returned, but it may happen
return new Update(0, null, true);
}
}
public enum UpdateError {
// No internet connection available
NETWORK_NOT_AVAILABLE,
// URL for version info is not valid
VERSION_URL_MALFORMED,
// Version info is invalid or unreachable
VERSION_ERROR,
// Download URL for update is not valid
INVALID_DOWNLOAD_URL
}
public interface UpdateListener {
void onSuccess(Update update);
void onError(UpdateError error);
}
}
================================================
FILE: app/src/main/java/app/lawnchair/lawnfeed/updater/UpdaterTask.java
================================================
package app.lawnchair.lawnfeed.updater;
import android.content.Context;
import android.os.AsyncTask;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
public class UpdaterTask extends AsyncTask {
private Context context;
private String update;
private Updater.UpdateListener listener;
public UpdaterTask(Context context, String update, Updater.UpdateListener listener) {
this.context = context;
this.update = update;
this.listener = listener;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
// No listener = no actions
if (listener == null) {
cancel(true);
return;
}
// Check if device is connected to the Internet
if (!Updater.isNetworkConnectivity(context)) {
listener.onError(Updater.UpdateError.NETWORK_NOT_AVAILABLE);
cancel(true);
return;
}
// Check if URL is valid
if (!Updater.isValidUrl(update)) {
listener.onError(Updater.UpdateError.VERSION_URL_MALFORMED);
cancel(true);
return;
}
}
@Override
protected Updater.Update doInBackground(Void... voids) {
// Our version.json from the storage server
JSONObject json = null;
try {
// Retrieve json object from URL
URL url = new URL(update);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
json = (JSONObject) new JSONParser().parse(new InputStreamReader(connection.getInputStream()));
} catch (IOException | ParseException ex) {
// Throw error if listener is not null
if (listener != null) {
listener.onError(Updater.UpdateError.VERSION_ERROR);
}
// Of course cancel the task
cancel(true);
return null;
}
// Don't continue if the json doesn't contain the last build number
if (!json.containsKey("travis_build_number")) {
return null;
}
int buildNumber = Integer.valueOf((String) json.get("travis_build_number"));
String version = (String) json.get("app_version");
String downloadUrl = String.format(Updater.DOWNLOAD_URL, version);
URL download = null;
try {
download = new URL(downloadUrl);
} catch (MalformedURLException ex) {
// Throw error if listener is not null
if (listener != null) {
listener.onError(Updater.UpdateError.INVALID_DOWNLOAD_URL);
}
// Of course cancel the task
cancel(true);
return null;
}
return new Updater.Update(buildNumber, download);
}
@Override
protected void onPostExecute(Updater.Update update) {
super.onPostExecute(update);
// Return fetched update to listener
if (listener != null) {
listener.onSuccess(update);
}
}
}
================================================
FILE: app/src/main/res/drawable/ic_lawnchair.xml
================================================
================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
================================================
================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
================================================
================================================
FILE: app/src/main/res/values/colors.xml
================================================
#3F51B5
#303F9F
#FF4081
================================================
FILE: app/src/main/res/values/strings.xml
================================================
Lawnfeed
Update available
A new version of Lawnfeed is available to download!
Downloading...
Error downloading file
Lawnfeed Updates
================================================
FILE: app/src/main/res/values/styles.xml
================================================
================================================
FILE: app/src/main/res/xml/file_paths.xml
================================================
================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.2.30'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
jcenter()
maven { url 'https://jitpack.io' }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Sat Mar 27 07:42:52 CET 2021
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-all.zip
================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
================================================
FILE: gradlew
================================================
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: settings.gradle
================================================
include ':app', ':launcherclient'