Initial commit to public repo
@@ -0,0 +1,24 @@
|
||||
package com.legacy.android
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.legacy.android", appContext.packageName)
|
||||
}
|
||||
}
|
||||
48
app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.legacy.android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<application
|
||||
android:name=".MyApplication"
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme.NoAction"
|
||||
android:usesCleartextTraffic="false"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".CustomServerActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.ProxidizeAndroidLegacy" />
|
||||
|
||||
<service
|
||||
android:name=".NotificationService"
|
||||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".LoginActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.ProxidizeAndroidLegacy" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
0
app/src/main/assets/config.ini
Normal file
0
app/src/main/assets/connection.log
Normal file
61
app/src/main/java/com/legacy/android/CustomServerActivity.kt
Normal file
@@ -0,0 +1,61 @@
|
||||
package com.legacy.android
|
||||
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.legacy.android.databinding.ActivityCustomServerBinding
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class CustomServerActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: ActivityCustomServerBinding
|
||||
private val preference by lazy { ServerPreference.getInstance(this) }
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
binding = ActivityCustomServerBinding.inflate(layoutInflater)
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(binding.root)
|
||||
binding.saveButton.setOnClickListener {
|
||||
if (isClear()) {
|
||||
Toast.makeText(this, "Saving server info", Toast.LENGTH_SHORT).show()
|
||||
lifecycleScope.launch {
|
||||
preference.saveProxyServer(
|
||||
ProxyServer(
|
||||
binding.etHost.text.toString(),
|
||||
binding.etPort.text.toString(),
|
||||
binding.etToken.text.toString()
|
||||
)
|
||||
)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.resetButton.setOnClickListener {
|
||||
Toast.makeText(this, "Proxy server set to default", Toast.LENGTH_SHORT).show()
|
||||
lifecycleScope.launch {
|
||||
delay(100)
|
||||
preference.clear()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun isClear(): Boolean {
|
||||
var clear = true
|
||||
if (binding.etHost.text.isNullOrEmpty()) {
|
||||
binding.etHost.error = "Enter a valid Host"
|
||||
clear = false
|
||||
} else binding.etHost.error = null
|
||||
if (binding.etPort.text.isNullOrEmpty()) {
|
||||
binding.etPort.error = "Enter a valid Port"
|
||||
clear = false
|
||||
} else binding.etPort.error = null
|
||||
if (binding.etToken.text.isNullOrEmpty()) {
|
||||
binding.etToken.error = "Enter a valid token"
|
||||
clear = false
|
||||
} else binding.etToken.error = null
|
||||
return clear
|
||||
}
|
||||
}
|
||||
321
app/src/main/java/com/legacy/android/LoginActivity.kt
Normal file
@@ -0,0 +1,321 @@
|
||||
package com.legacy.android
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.app.Notification
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.app.PendingIntent.FLAG_MUTABLE
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.os.*
|
||||
import android.provider.Settings
|
||||
import android.text.Html
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.legacy.android.databinding.ActivityLoginBinding
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.*
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.system.exitProcess
|
||||
import frpclib.Frpclib as Conn
|
||||
|
||||
|
||||
class LoginActivity : AppCompatActivity() {
|
||||
private var user: String? = null
|
||||
private var pwd: String? = null
|
||||
private var notificationManager: NotificationManagerCompat? = null
|
||||
private val mLabel = "copy"
|
||||
private var mText: String = ""
|
||||
private var mRandomPort = 0
|
||||
|
||||
private var server: ProxyServer = ProxyServer.default()
|
||||
|
||||
private lateinit var binding: ActivityLoginBinding
|
||||
private val preference by lazy { ServerPreference.getInstance(this) }
|
||||
|
||||
@SuppressLint("BatteryLife")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityLoginBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED){
|
||||
preference.SERVER.collect{
|
||||
server = it
|
||||
}
|
||||
}
|
||||
}
|
||||
notificationManager = NotificationManagerCompat.from(this)
|
||||
val pm = getSystemService(PowerManager::class.java)
|
||||
if (!pm.isIgnoringBatteryOptimizations(packageName)) {
|
||||
val i = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
|
||||
.setData(Uri.parse("package:$packageName"))
|
||||
startActivity(i)
|
||||
}
|
||||
binding.copyButton.setOnClickListener {
|
||||
val clipboard = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clip = ClipData.newPlainText(mLabel, mText)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
Toast.makeText(this@LoginActivity, "Copied", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
|
||||
binding.stop.setOnClickListener {
|
||||
AlertDialog.Builder(this@LoginActivity)
|
||||
.setCancelable(false)
|
||||
.setTitle("Confirm Exit")
|
||||
.setMessage("Are you sure, You want to exit")
|
||||
.setPositiveButton("sure") { d, _ ->
|
||||
stopService(Intent(applicationContext, NotificationService::class.java))
|
||||
d.dismiss()
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
exitProcess(0)
|
||||
}, 100)
|
||||
}.setNegativeButton("NO", null)
|
||||
.create()
|
||||
.show()
|
||||
}
|
||||
binding.connect.setOnClickListener {
|
||||
writeToConfigFile(server)
|
||||
Toast.makeText(this@LoginActivity, "Connecting to proxy server", Toast.LENGTH_LONG)
|
||||
.show()
|
||||
startConnection()
|
||||
Handler(Looper.getMainLooper()).postDelayed({ checkIfPortUsed() }, 3000)
|
||||
}
|
||||
|
||||
binding.customServer.setOnClickListener{
|
||||
startActivity(Intent(this, CustomServerActivity::class.java))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private fun checkIfPortUsed(): Boolean {
|
||||
var connectionStatus = true
|
||||
val logfile = File(filesDir, MyApplication.LOGFILE)
|
||||
if (logfile.exists()) {
|
||||
try {
|
||||
val inputStream: InputStream = assets.open(MyApplication.LOGFILE)
|
||||
var line: String? = null
|
||||
val br = BufferedReader(FileReader(logfile))
|
||||
var isFileEmpty = true
|
||||
while ((br.readLine()?.also { line = it }) != null) {
|
||||
isFileEmpty = false
|
||||
if (line?.contains("port already used") == true) {
|
||||
connectionStatus = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!connectionStatus) {
|
||||
Toast.makeText(
|
||||
this@LoginActivity,
|
||||
"Port is already used. Try again",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
} else if (isFileEmpty) {
|
||||
Toast.makeText(
|
||||
this@LoginActivity,
|
||||
"Not connected. Restart app and connect again",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
} else {
|
||||
showConnectionDetails()
|
||||
val bundle = Bundle().also {
|
||||
it.putInt(PORT, mRandomPort)
|
||||
it.putString(USER, user)
|
||||
it.putString(PASS, pwd)
|
||||
}
|
||||
Toast.makeText(this@LoginActivity, "connection success", Toast.LENGTH_LONG)
|
||||
.show()
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
val sdf = SimpleDateFormat("HH:mm:ss z")
|
||||
val currentTime = sdf.format(Date())
|
||||
val largeIcon = BitmapFactory.decodeResource(resources, R.drawable.logo)
|
||||
val intent1 = Intent(this, NotificationService::class.java)
|
||||
intent1.action = "close_service"
|
||||
var pIntent: PendingIntent? = null
|
||||
pIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
PendingIntent.getService(this, 0, intent1, FLAG_MUTABLE)
|
||||
} else PendingIntent.getService(this, 0, intent1, 0)
|
||||
|
||||
val p2Inten = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
PendingIntent.getActivity(
|
||||
this,
|
||||
2,
|
||||
Intent(this, LoginActivity::class.java),
|
||||
FLAG_MUTABLE
|
||||
)
|
||||
} else {
|
||||
PendingIntent.getActivity(
|
||||
this,
|
||||
2,
|
||||
Intent(this, LoginActivity::class.java),
|
||||
0
|
||||
)
|
||||
}
|
||||
val notification = NotificationCompat.Builder(
|
||||
applicationContext,
|
||||
MyApplication.channel_1_id
|
||||
)
|
||||
.setSmallIcon(R.drawable.bill)
|
||||
.setLargeIcon(largeIcon)
|
||||
.setStyle(NotificationCompat.BigPictureStyle())
|
||||
.setStyle(NotificationCompat.BigTextStyle())
|
||||
.setContentIntent(p2Inten)
|
||||
.setContentTitle("Connected")
|
||||
.setExtras(bundle)
|
||||
.addAction(
|
||||
NotificationCompat.Action(
|
||||
R.drawable.ic_baseline_close_24,
|
||||
"Close",
|
||||
pIntent
|
||||
)
|
||||
)
|
||||
.setContentText(
|
||||
"You are Connected to (${server.host}:$mRandomPort:$user:$pwd)On time$currentTime"
|
||||
)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setCategory(NotificationCompat.CATEGORY_ALARM)
|
||||
.setColor(Color.YELLOW).build()
|
||||
notificationManager?.notify(NOTIFICATION_ID, notification)
|
||||
}, 2000)
|
||||
val servicesIntent = Intent(applicationContext, NotificationService::class.java)
|
||||
servicesIntent.putExtra("stat", "Proxidize Android is running!!")
|
||||
startService(servicesIntent)
|
||||
}
|
||||
br.close()
|
||||
inputStream.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
return connectionStatus
|
||||
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
val notification = getActiveNotification() ?: return
|
||||
val extras = notification.extras
|
||||
pwd = extras.getString(PASS)
|
||||
user = extras.getString(USER)
|
||||
mRandomPort = extras.getInt(PORT)
|
||||
showConnectionDetails()
|
||||
|
||||
}
|
||||
|
||||
private fun showConnectionDetails() {
|
||||
mText = "${server.host}:$mRandomPort:$user:$pwd"
|
||||
val details =
|
||||
"<b>IP</b> : ${server.host}<br><b>Port</b> : $mRandomPort<br><b>Username</b> : $user<br><b>Password</b> : $pwd"
|
||||
binding.connection.text = mText
|
||||
binding.connectionTextView.text = Html.fromHtml(details)
|
||||
binding.connect.text = getString(R.string.connected)
|
||||
}
|
||||
|
||||
private fun startConnection() {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
Conn.touch()
|
||||
Conn.run("$filesDir/config.ini")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getActiveNotification(): Notification? {
|
||||
val notificationManager =
|
||||
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
val barNotifications = notificationManager.activeNotifications
|
||||
for (notification in barNotifications) {
|
||||
if (notification.id == NOTIFICATION_ID) {
|
||||
return notification.notification
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun writeToConfigFile(server: ProxyServer) {
|
||||
// re-create connection.log file
|
||||
val logFile = File(filesDir, MyApplication.LOGFILE)
|
||||
if (logFile.exists()) {
|
||||
logFile.delete()
|
||||
}
|
||||
if (!logFile.exists()) {
|
||||
try {
|
||||
logFile.createNewFile()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
var out: FileOutputStream? = null
|
||||
val file = File(filesDir, MyApplication.FILENAME)
|
||||
if (file.exists()) {
|
||||
file.delete()
|
||||
}
|
||||
if (!file.exists()) {
|
||||
try {
|
||||
file.createNewFile()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
try {
|
||||
out = FileOutputStream(file, true)
|
||||
mRandomPort = getRandomPort()
|
||||
user = getAlphaNumericString()
|
||||
pwd = getAlphaNumericString()
|
||||
out.write("[common]\r\n".toByteArray())
|
||||
out.write("server_addr = ${server.host}\r\n".toByteArray())
|
||||
out.write("server_port = ${server.port}\r\n".toByteArray())
|
||||
out.write("token = ${server.token}\r\n".toByteArray())
|
||||
out.write("admin_addr = 0.0.0.0\r\n".toByteArray())
|
||||
out.write("admin_port = 7400\r\n".toByteArray())
|
||||
out.write("admin_user = admin\r\n".toByteArray())
|
||||
out.write("admin_passwd = admin\r\n".toByteArray())
|
||||
out.write("log_file = ${logFile.absolutePath}\r\n".toByteArray())
|
||||
out.write("log_level = info\r\n".toByteArray())
|
||||
out.write("log_max_days = 3\r\n".toByteArray())
|
||||
out.write("pool_count = 5\r\n".toByteArray())
|
||||
out.write("tcp_mux = true\r\n".toByteArray())
|
||||
out.write("login_fail_exit = true\r\n".toByteArray())
|
||||
out.write("protocol = tcp\r\n".toByteArray())
|
||||
out.write("[android_proxy_$mRandomPort]\r\n".toByteArray())
|
||||
out.write("type=tcp\r\n".toByteArray())
|
||||
out.write("remote_port=$mRandomPort\r\n".toByteArray())
|
||||
out.write("plugin=http_proxy\r\n".toByteArray())
|
||||
out.write("plugin_http_user=$user\r\n".toByteArray())
|
||||
out.write("plugin_http_passwd=$pwd\r\n".toByteArray())
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
try {
|
||||
out?.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
const val NOTIFICATION_ID = 20340
|
||||
const val IP = "IP"
|
||||
const val PORT = "PORT"
|
||||
const val USER = "USER"
|
||||
const val PASS = "PASS"
|
||||
}
|
||||
}
|
||||
43
app/src/main/java/com/legacy/android/MainActivity.kt
Normal file
@@ -0,0 +1,43 @@
|
||||
package com.legacy.android
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.animation.Animation
|
||||
import android.view.animation.AnimationUtils
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
class MainActivity : Activity() {
|
||||
|
||||
//VARIABLES
|
||||
var tobAnim: Animation? = null
|
||||
var bottomAnim: Animation? = null
|
||||
var image: ImageView? = null
|
||||
var slogan: TextView? = null
|
||||
|
||||
var mHandler = Handler(Looper.getMainLooper())
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
|
||||
tobAnim = AnimationUtils.loadAnimation(this, R.anim.tob_anim)
|
||||
bottomAnim = AnimationUtils.loadAnimation(this, R.anim.bottom_anim)
|
||||
|
||||
image = findViewById(R.id.logo)
|
||||
slogan = findViewById(R.id.slogan1)
|
||||
|
||||
image?.animation = tobAnim
|
||||
slogan?.animation = bottomAnim
|
||||
|
||||
mHandler.postDelayed({
|
||||
val intent = Intent(this@MainActivity, LoginActivity::class.java)
|
||||
startActivity(intent)
|
||||
finish()
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
99
app/src/main/java/com/legacy/android/MyApplication.java
Normal file
@@ -0,0 +1,99 @@
|
||||
package com.legacy.android;
|
||||
|
||||
import android.app.Application;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.os.Build;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class MyApplication extends Application {
|
||||
|
||||
public static Context mContext;
|
||||
public static String FILENAME = "config.ini";
|
||||
public static String LOGFILE = "connection.log";
|
||||
public static final String channel_1_id = "channel1";
|
||||
public static final String channel_2_id = "servicechannel";
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
mContext = getApplicationContext();
|
||||
createnotficationchannel();
|
||||
copyToSD("config.ini");
|
||||
}
|
||||
|
||||
private void createnotficationchannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel1 = new NotificationChannel(channel_1_id, "channel 1"
|
||||
, NotificationManager.IMPORTANCE_HIGH);
|
||||
channel1.setDescription("give the user some important information about the connection");
|
||||
channel1.enableLights(true);
|
||||
channel1.enableVibration(true);
|
||||
NotificationChannel servicechannel = new NotificationChannel(channel_2_id, "channel 1"
|
||||
, NotificationManager.IMPORTANCE_DEFAULT);
|
||||
servicechannel.setDescription("this channel is for a foreground service to let the user aware " +
|
||||
"when he is connected to the proxy and that the application in working background");
|
||||
|
||||
|
||||
NotificationManager manager = getSystemService(NotificationManager.class);
|
||||
manager.createNotificationChannel(channel1);
|
||||
manager.createNotificationChannel(servicechannel);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void copyToSD(String dbName) {
|
||||
InputStream in = null;
|
||||
FileOutputStream out = null;
|
||||
File file = new File(this.getFilesDir(), dbName);
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
//create a new file
|
||||
try {
|
||||
file.createNewFile();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
//the assest manager is for reading the file in raw form
|
||||
AssetManager assets = getAssets();
|
||||
|
||||
try {
|
||||
in = assets.open(dbName);
|
||||
out = new FileOutputStream(file);
|
||||
byte[] b = new byte[1024];
|
||||
int len = -1;
|
||||
while ((len = in.read(b)) != -1) {
|
||||
out.write(b, 0, len);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
|
||||
e.printStackTrace();
|
||||
} finally {///close the input and output stream
|
||||
|
||||
if (in != null) {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
67
app/src/main/java/com/legacy/android/NotificationService.kt
Normal file
@@ -0,0 +1,67 @@
|
||||
package com.legacy.android
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.PowerManager
|
||||
import androidx.core.app.NotificationCompat
|
||||
|
||||
class NotificationService : Service() {
|
||||
|
||||
private val wakeLock: PowerManager.WakeLock by lazy {
|
||||
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
|
||||
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::MyWakelockTag")
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("WakelockTimeout")
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
wakeLock.acquire()
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
if ("close_service" == intent.action) {
|
||||
stopSelf()
|
||||
System.exit(0)
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
val intent1 = Intent(this, NotificationService::class.java)
|
||||
intent1.action = "close_service"
|
||||
var pIntent: PendingIntent? = null
|
||||
pIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
PendingIntent.getService(this, 0, intent1, PendingIntent.FLAG_MUTABLE)
|
||||
} else PendingIntent.getService(this, 0, intent1, 0)
|
||||
val stat = intent.getStringExtra("stat")
|
||||
val notification =
|
||||
NotificationCompat.Builder(applicationContext, MyApplication.channel_2_id)
|
||||
.setContentTitle("Service working")
|
||||
.setContentText(stat)
|
||||
.addAction(
|
||||
NotificationCompat.Action(
|
||||
R.drawable.ic_baseline_close_24,
|
||||
"Close",
|
||||
pIntent
|
||||
)
|
||||
)
|
||||
.setSmallIcon(R.drawable.ic_baseline_miscellaneous_services_24)
|
||||
.build()
|
||||
startForeground(LoginActivity.NOTIFICATION_ID, notification)
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (wakeLock.isHeld) {
|
||||
wakeLock.release()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
12
app/src/main/java/com/legacy/android/ProxyServer.kt
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.legacy.android
|
||||
|
||||
data class ProxyServer(val host: String, val port: String, val token: String) {
|
||||
|
||||
companion object {
|
||||
const val HOST = "138.201.246.49"
|
||||
const val SERVER_PORT = "2000"
|
||||
const val TOKEN = "12345678"
|
||||
|
||||
fun default() = ProxyServer(HOST, SERVER_PORT, TOKEN)
|
||||
}
|
||||
}
|
||||
51
app/src/main/java/com/legacy/android/ServerPreference.kt
Normal file
@@ -0,0 +1,51 @@
|
||||
package com.legacy.android
|
||||
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class ServerPreference private constructor(private val context: Context) {
|
||||
|
||||
private val Context.dataStore by preferencesDataStore(name = "settings")
|
||||
|
||||
|
||||
val SERVER: Flow<ProxyServer> = context.dataStore.data.map(::retrieveServer)
|
||||
|
||||
|
||||
private fun retrieveServer(pref: Preferences): ProxyServer {
|
||||
if (pref[stringPreferencesKey("host")].isNullOrBlank()) return ProxyServer.default()
|
||||
return ProxyServer(
|
||||
pref[stringPreferencesKey("host")] ?: "",
|
||||
pref[stringPreferencesKey("port")] ?: "", pref[stringPreferencesKey("token")] ?: ""
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
suspend fun clear() {
|
||||
context.dataStore.edit { it.clear() }
|
||||
}
|
||||
|
||||
suspend fun saveProxyServer(device: ProxyServer) {
|
||||
context.dataStore.edit { pref ->
|
||||
pref[stringPreferencesKey("host")] = device.host
|
||||
pref[stringPreferencesKey("port")] = device.port
|
||||
pref[stringPreferencesKey("token")] = device.token
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
var INSTANCE: ServerPreference? = null
|
||||
fun getInstance(context: Context): ServerPreference {
|
||||
return INSTANCE ?: ServerPreference(context).also { INSTANCE = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
app/src/main/java/com/legacy/android/Utils.kt
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.legacy.android
|
||||
|
||||
|
||||
fun getRandomPort(from: Int = 4000, to: Int = 60000): Int {
|
||||
return ((Math.random() * (to - from)).toInt()) + from
|
||||
}
|
||||
|
||||
fun getAlphaNumericString(length: Int = 4): String {
|
||||
val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9')
|
||||
return (1..length)
|
||||
.map { allowedChars.random() }
|
||||
.joinToString("")
|
||||
}
|
||||
16
app/src/main/res/anim/bottom_anim.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<translate
|
||||
android:fromXDelta="0%"
|
||||
android:fromYDelta="100%"
|
||||
android:duration = "1500"
|
||||
/>
|
||||
|
||||
<alpha
|
||||
android:fromAlpha="0.1"
|
||||
android:toAlpha="1.0"
|
||||
android:duration = "1500"
|
||||
/>
|
||||
|
||||
</set>
|
||||
16
app/src/main/res/anim/tob_anim.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<translate
|
||||
android:fromXDelta="0%"
|
||||
android:fromYDelta="-50%"
|
||||
android:duration = "1500"
|
||||
/>
|
||||
|
||||
<alpha
|
||||
android:fromAlpha="0.1"
|
||||
android:toAlpha="1.0"
|
||||
android:duration = "1500"
|
||||
/>
|
||||
|
||||
</set>
|
||||
BIN
app/src/main/res/drawable/bill.png
Normal file
|
After Width: | Height: | Size: 772 B |
27
app/src/main/res/drawable/button_rectangle_pink.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_pressed="true">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#F3E40D"/>
|
||||
<corners
|
||||
android:topRightRadius="20dp"
|
||||
android:topLeftRadius="20dp"
|
||||
android:bottomRightRadius="20dp"
|
||||
android:bottomLeftRadius="20dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#Fff"/>
|
||||
<corners
|
||||
android:topRightRadius="20dp"
|
||||
android:topLeftRadius="20dp"
|
||||
android:bottomRightRadius="20dp"
|
||||
android:bottomLeftRadius="20dp"/>
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
</selector>
|
||||
BIN
app/src/main/res/drawable/ic_action_name.png
Normal file
|
After Width: | Height: | Size: 283 B |
5
app/src/main/res/drawable/ic_baseline_close_24.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,6 @@
|
||||
<vector android:height="40dp" android:tint="?attr/colorControlNormal"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="40dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M14.17,13.71l1.4,-2.42c0.09,-0.15 0.05,-0.34 -0.08,-0.45l-1.48,-1.16c0.03,-0.22 0.05,-0.45 0.05,-0.68s-0.02,-0.46 -0.05,-0.69l1.48,-1.16c0.13,-0.11 0.17,-0.3 0.08,-0.45l-1.4,-2.42c-0.09,-0.15 -0.27,-0.21 -0.43,-0.15L12,4.83c-0.36,-0.28 -0.75,-0.51 -1.18,-0.69l-0.26,-1.85C10.53,2.13 10.38,2 10.21,2h-2.8C7.24,2 7.09,2.13 7.06,2.3L6.8,4.15C6.38,4.33 5.98,4.56 5.62,4.84l-1.74,-0.7c-0.16,-0.06 -0.34,0 -0.43,0.15l-1.4,2.42C1.96,6.86 2,7.05 2.13,7.16l1.48,1.16C3.58,8.54 3.56,8.77 3.56,9s0.02,0.46 0.05,0.69l-1.48,1.16C2,10.96 1.96,11.15 2.05,11.3l1.4,2.42c0.09,0.15 0.27,0.21 0.43,0.15l1.74,-0.7c0.36,0.28 0.75,0.51 1.18,0.69l0.26,1.85C7.09,15.87 7.24,16 7.41,16h2.8c0.17,0 0.32,-0.13 0.35,-0.3l0.26,-1.85c0.42,-0.18 0.82,-0.41 1.18,-0.69l1.74,0.7C13.9,13.92 14.08,13.86 14.17,13.71zM8.81,11c-1.1,0 -2,-0.9 -2,-2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2C10.81,10.1 9.91,11 8.81,11z"/>
|
||||
<path android:fillColor="@android:color/white" android:pathData="M21.92,18.67l-0.96,-0.74c0.02,-0.14 0.04,-0.29 0.04,-0.44c0,-0.15 -0.01,-0.3 -0.04,-0.44l0.95,-0.74c0.08,-0.07 0.11,-0.19 0.05,-0.29l-0.9,-1.55c-0.05,-0.1 -0.17,-0.13 -0.28,-0.1l-1.11,0.45c-0.23,-0.18 -0.48,-0.33 -0.76,-0.44l-0.17,-1.18C18.73,13.08 18.63,13 18.53,13h-1.79c-0.11,0 -0.21,0.08 -0.22,0.19l-0.17,1.18c-0.27,0.12 -0.53,0.26 -0.76,0.44l-1.11,-0.45c-0.1,-0.04 -0.22,0 -0.28,0.1l-0.9,1.55c-0.05,0.1 -0.04,0.22 0.05,0.29l0.95,0.74c-0.02,0.14 -0.03,0.29 -0.03,0.44c0,0.15 0.01,0.3 0.03,0.44l-0.95,0.74c-0.08,0.07 -0.11,0.19 -0.05,0.29l0.9,1.55c0.05,0.1 0.17,0.13 0.28,0.1l1.11,-0.45c0.23,0.18 0.48,0.33 0.76,0.44l0.17,1.18c0.02,0.11 0.11,0.19 0.22,0.19h1.79c0.11,0 0.21,-0.08 0.22,-0.19l0.17,-1.18c0.27,-0.12 0.53,-0.26 0.75,-0.44l1.12,0.45c0.1,0.04 0.22,0 0.28,-0.1l0.9,-1.55C22.03,18.86 22,18.74 21.92,18.67zM17.63,18.83c-0.74,0 -1.35,-0.6 -1.35,-1.35s0.6,-1.35 1.35,-1.35s1.35,0.6 1.35,1.35S18.37,18.83 17.63,18.83z"/>
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/ic_baseline_token_24.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M19.97,6.43L12,2L4.03,6.43L9.1,9.24C9.83,8.48 10.86,8 12,8s2.17,0.48 2.9,1.24L19.97,6.43zM10,12c0,-1.1 0.9,-2 2,-2s2,0.9 2,2s-0.9,2 -2,2S10,13.1 10,12zM11,21.44L3,17V8.14l5.13,2.85C8.04,11.31 8,11.65 8,12c0,1.86 1.27,3.43 3,3.87V21.44zM13,21.44v-5.57c1.73,-0.44 3,-2.01 3,-3.87c0,-0.35 -0.04,-0.69 -0.13,-1.01L21,8.14L21,17L13,21.44z"/>
|
||||
</vector>
|
||||
BIN
app/src/main/res/drawable/icon_password.png
Normal file
|
After Width: | Height: | Size: 331 B |
BIN
app/src/main/res/drawable/logo.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
app/src/main/res/drawable/port.png
Normal file
|
After Width: | Height: | Size: 566 B |
BIN
app/src/main/res/drawable/server.png
Normal file
|
After Width: | Height: | Size: 243 B |
131
app/src/main/res/layout/activity_custom_server.xml
Normal file
@@ -0,0 +1,131 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#F8F8D1"
|
||||
tools:context=".CustomServerActivity">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/colorPrimaryDark"
|
||||
android:gravity="center"
|
||||
android:text="@string/app_name"
|
||||
android:textColor="@color/black"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:title="" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/host"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:hint="@string/host"
|
||||
|
||||
app:startIconDrawable="@drawable/server"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/toolbar">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/et_host"
|
||||
android:inputType="phone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/bind_port"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:hint="@string/binding_port"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:startIconDrawable="@drawable/port"
|
||||
app:layout_constraintTop_toBottomOf="@+id/host">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/et_port"
|
||||
android:layout_width="match_parent"
|
||||
android:inputType="number"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/token"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:hint="@string/token"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:startIconDrawable="@drawable/ic_baseline_token_24"
|
||||
app:layout_constraintTop_toBottomOf="@+id/bind_port">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/et_token"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/saveButton"
|
||||
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:elevation="8dp"
|
||||
android:padding="16dp"
|
||||
android:text="@string/save"
|
||||
android:textColor="#241708"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="@color/white"
|
||||
app:cornerRadius="16dp"
|
||||
app:elevation="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/token" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/resetButton"
|
||||
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:elevation="8dp"
|
||||
android:padding="16dp"
|
||||
android:text="@string/reset"
|
||||
android:textColor="#241708"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="@color/white"
|
||||
app:cornerRadius="16dp"
|
||||
app:elevation="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/saveButton" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
18
app/src/main/res/layout/activity_log.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#F3E40D">
|
||||
|
||||
|
||||
<include
|
||||
layout="@layout/log_layout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true" />
|
||||
|
||||
|
||||
</RelativeLayout>
|
||||
175
app/src/main/res/layout/activity_login.xml
Normal file
@@ -0,0 +1,175 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/linear"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#F8F8D1">
|
||||
<TextView
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/colorPrimaryDark"
|
||||
app:title=""
|
||||
android:text="@string/app_name"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/black"
|
||||
android:gravity="center"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
/>
|
||||
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/connect"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:elevation="8dp"
|
||||
android:padding="16dp"
|
||||
android:stateListAnimator="@null"
|
||||
android:text="@string/connect"
|
||||
android:textColor="#241708"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="@color/white"
|
||||
app:cornerRadius="16dp"
|
||||
app:elevation="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
|
||||
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/copyButton"
|
||||
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:elevation="8dp"
|
||||
android:padding="16dp"
|
||||
android:text="@string/copy_proxy_to_clipboard"
|
||||
android:textColor="#241708"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="@color/white"
|
||||
app:cornerRadius="16dp"
|
||||
app:elevation="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/connect" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/customServer"
|
||||
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:elevation="8dp"
|
||||
android:padding="16dp"
|
||||
android:text="@string/custom_server"
|
||||
android:textColor="#241708"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="@color/white"
|
||||
app:cornerRadius="16dp"
|
||||
app:elevation="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/copyButton" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/connectionLabelTextView"
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="31dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="15dp"
|
||||
android:fontFamily="sans-serif"
|
||||
android:text="@string/your_proxy_connection_details"
|
||||
android:textColor="#241708"
|
||||
android:textIsSelectable="false"
|
||||
android:textSize="20sp"
|
||||
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/customServer" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/connection"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:textAlignment="center"
|
||||
android:textColor="#0202C3"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/connectionLabelTextView" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/connectionTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:textAlignment="textStart"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/wbsiteLink"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/connection"
|
||||
app:layout_constraintVertical_bias="0.20"
|
||||
tools:text="@string/proxy" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/wbsiteLink"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:autoLink="web"
|
||||
android:text="@string/learn_about_mobile_proxies_on_proxidize_com"
|
||||
android:textAlignment="center"
|
||||
android:textColor="#0202C3"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/stop"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/stop"
|
||||
style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="54dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:elevation="8dp"
|
||||
android:text="@string/exit"
|
||||
android:textColor="#241708"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="@color/white"
|
||||
app:cornerRadius="16dp"
|
||||
app:elevation="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
46
app/src/main/res/layout/activity_main.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="#F3E40D">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/logo"
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="300dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="75dp"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/logo"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/slogan1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/_4g_proxy_solutions"
|
||||
android:textAlignment="center"
|
||||
android:textColor="#000"
|
||||
android:textSize="18dp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<!--<TextView-->
|
||||
<!--android:layout_width="match_parent"-->
|
||||
<!--android:layout_height="wrap_content"-->
|
||||
<!--android:layout_gravity="bottom|center"-->
|
||||
<!--android:gravity="center"-->
|
||||
<!--android:layout_marginBottom="5dp"-->
|
||||
<!--android:textColor="@android:color/holo_red_dark"-->
|
||||
<!--android:text="©2016~2018 fun"/>-->
|
||||
</RelativeLayout>
|
||||
69
app/src/main/res/layout/log_layout.xml
Normal file
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_user"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
app:srcCompat="@drawable/logo" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_email"
|
||||
android:drawableLeft="@drawable/ic_action_name"
|
||||
android:textColor="#241708"
|
||||
android:background="#115A3131"
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_below="@+id/iv_user"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="40dp"
|
||||
android:ems="10"
|
||||
android:inputType="textEmailAddress" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_password"
|
||||
android:textColor="#241708"
|
||||
android:drawableLeft="@drawable/icon_password"
|
||||
android:background="#11000000"
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_alignLeft="@+id/et_email"
|
||||
android:layout_alignStart="@+id/et_email"
|
||||
android:layout_below="@+id/et_email"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:ems="10"
|
||||
android:inputType="textPassword" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_login"
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="45dp"
|
||||
android:background="@drawable/button_rectangle_pink"
|
||||
android:textColor="#241708"
|
||||
android:textSize="20sp"
|
||||
android:layout_below="@+id/et_password"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="40dp"
|
||||
android:text="Login" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_register"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/btn_login"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="20dp"
|
||||
|
||||
android:textSize="15sp"
|
||||
android:textColor="#241708"
|
||||
android:text="Register Me" />
|
||||
|
||||
</RelativeLayout>
|
||||
4
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<foreground android:drawable="@mipmap/ic_launcher_adaptive_fore"/>
|
||||
</adaptive-icon>
|
||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_adaptive_fore.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_adaptive_fore.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_adaptive_fore.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_adaptive_fore.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_adaptive_fore.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
15
app/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
|
||||
<color name="colorPrimary">#000000</color>
|
||||
<color name="colorPrimaryDark">#FFE500</color>
|
||||
<color name="colorAccent">#000000</color>
|
||||
<color name="divider">#e9e9e9</color>
|
||||
</resources>
|
||||
44
app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<resources>
|
||||
<string name="app_name">Proxidize Android Legacy</string>
|
||||
<string name="_4g_proxy_solutions">4G Proxy Solutions</string>
|
||||
|
||||
<!-- Strings related to login -->
|
||||
<string name="app_lb">Proxidize</string>
|
||||
<string name="action_sign_in_short">Sign in</string>
|
||||
<string name="error_invalid_email">This email address is invalid</string>
|
||||
<string name="error_field_required">This field is required</string>
|
||||
<string name="permission_rationale">"Contacts permissions are needed for providing email
|
||||
completions."
|
||||
</string>
|
||||
<array name="wlan_type">
|
||||
<item>TCP</item>
|
||||
<item>UDP</item>
|
||||
<item>HTTP</item>
|
||||
<item>HTTPS</item>
|
||||
</array>
|
||||
<string name="title_activity_navigtion_menu">NavigtionMenu</string>
|
||||
<string name="navigation_drawer_open">Open navigation drawer</string>
|
||||
<string name="navigation_drawer_close">Close navigation drawer</string>
|
||||
<string name="nav_header_title">Android Studio</string>
|
||||
<string name="nav_header_subtitle">android.studio@android.com</string>
|
||||
<string name="nav_header_desc">Navigation header</string>
|
||||
<string name="action_settings">Settings</string>
|
||||
|
||||
<string name="menu_home">Home</string>
|
||||
<string name="menu_gallery">Gallery</string>
|
||||
<string name="menu_slideshow">Slideshow</string>
|
||||
<string name="title_activity_drawer">DrawerActivity</string>
|
||||
<string name="proxy"><b>IP</b> : %1$s\n<b>Port</b> : %2$d\n<b>Username</b> : %3$s\n<b>Password</b> : %4$s</string>
|
||||
<string name="connect">Connect</string>
|
||||
<string name="copy_proxy_to_clipboard">Copy Proxy To Clipboard</string>
|
||||
<string name="your_proxy_connection_details">Your Proxy Connection Details:</string>
|
||||
<string name="exit">Exit</string>
|
||||
<string name="learn_about_mobile_proxies_on_proxidize_com">Learn about mobile proxies on proxidize.com</string>
|
||||
<string name="connected">Connected</string>
|
||||
<string name="save">Save</string>
|
||||
<string name="reset">Reset</string>
|
||||
<string name="custom_server">Custom Server</string>
|
||||
<string name="host">Host</string>
|
||||
<string name="binding_port">Binding Port</string>
|
||||
<string name="token">Token</string>
|
||||
</resources>
|
||||
119
app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,119 @@
|
||||
<resources>
|
||||
|
||||
|
||||
<style name="AppTheme.NoAction" parent="android:Theme">
|
||||
<item name="android:windowTitleBackgroundStyle">@style/WindowTitleBackground</item>
|
||||
<!--
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
-->
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowTitleSize">50dp</item>
|
||||
<item name="android:windowBackground">#F8E00C</item>
|
||||
<item name="android:textColor">#241708</item>
|
||||
<item name="android:textAlignment">center</item>
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="android:windowTitleBackgroundStyle">@style/WindowTitleBackground</item>
|
||||
<!--
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
-->
|
||||
<item name="android:windowTitleSize">50dp</item>
|
||||
<item name="android:textColor">#241708</item>
|
||||
<item name="android:textAlignment">center</item>
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<style name="exit_dialog_style">
|
||||
<item name="android:layout_gravity">center</item>
|
||||
<item name="android:textColor">#f6f900</item>
|
||||
<item name="android:colorBackground">#F8E00C</item>
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<style name="WindowTitleBackground">
|
||||
<item name="android:background">#F8E00C</item>
|
||||
<item name="android:textColor">#241708</item>
|
||||
<item name="android:layout_gravity">center</item>
|
||||
</style>
|
||||
|
||||
<style name="TitleTextView">
|
||||
<item name="android:textColor">#241708</item>
|
||||
<item name="android:textSize">16sp</item>
|
||||
<item name="android:layout_gravity">center</item>
|
||||
|
||||
</style>
|
||||
|
||||
<style name="ItemLayout">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">44dp</item>
|
||||
<item name="android:gravity">center_vertical</item>
|
||||
<item name="android:paddingLeft">2dp</item>
|
||||
<item name="android:paddingRight">8dp</item>
|
||||
</style>
|
||||
|
||||
<style name="Label">
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:layout_gravity">center_vertical</item>
|
||||
<item name="android:textColor">@android:color/black</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
</style>
|
||||
|
||||
<style name="BlueBtn">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">40dp</item>
|
||||
<item name="android:gravity">center</item>
|
||||
<item name="android:layout_marginLeft">8dp</item>
|
||||
<item name="android:layout_marginRight">8dp</item>
|
||||
<item name="android:textColor">@android:color/white</item>
|
||||
<item name="android:textSize">16sp</item>
|
||||
</style>
|
||||
|
||||
<style name="blackBtn">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">40dp</item>
|
||||
<item name="android:gravity">center</item>
|
||||
<item name="android:layout_marginLeft">8dp</item>
|
||||
<item name="android:layout_marginRight">8dp</item>
|
||||
<item name="android:textColor">@android:color/black</item>
|
||||
<item name="android:textSize">16sp</item>
|
||||
</style>
|
||||
|
||||
<style name="InputEditText">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">match_parent</item>
|
||||
<item name="android:gravity">center_vertical|right</item>
|
||||
<item name="android:textColor">@android:color/black</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
<item name="android:layout_marginLeft">8dp</item>
|
||||
<item name="android:singleLine">true</item>
|
||||
<item name="android:background">@null</item>
|
||||
</style>
|
||||
|
||||
<style name="ActionText">
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:gravity">center</item>
|
||||
<item name="android:minWidth">64dp</item>
|
||||
<item name="android:minHeight">36dp</item>
|
||||
<item name="android:textColor">@android:color/holo_blue_dark</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
</style>
|
||||
|
||||
<style name="BlueBtnNoRoundCorner">
|
||||
<item name="android:gravity">center</item>
|
||||
|
||||
<item name="android:textColor">@android:color/white</item>
|
||||
<item name="android:textSize">16sp</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||
|
||||
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
|
||||
</resources>
|
||||
16
app/src/main/res/values/themes.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.ProxidizeAndroidLegacy" parent="Theme.MaterialComponents.Light.NoActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryVariant">@color/colorPrimaryDark</item>
|
||||
<item name="colorOnPrimary">@color/white</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/colorPrimaryDark</item>
|
||||
<item name="colorSecondaryVariant">@color/colorPrimaryDark</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" >?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
||||
13
app/src/main/res/xml/backup_rules.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample backup rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/guide/topics/data/autobackup
|
||||
for details.
|
||||
Note: This file is ignored for devices older that API 31
|
||||
See https://developer.android.com/about/versions/12/backup-restore
|
||||
-->
|
||||
<full-backup-content>
|
||||
<!--
|
||||
<include domain="sharedpref" path="."/>
|
||||
<exclude domain="sharedpref" path="device.xml"/>
|
||||
-->
|
||||
</full-backup-content>
|
||||
19
app/src/main/res/xml/data_extraction_rules.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample data extraction rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||
for details.
|
||||
-->
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
-->
|
||||
</cloud-backup>
|
||||
<!--
|
||||
<device-transfer>
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
</device-transfer>
|
||||
-->
|
||||
</data-extraction-rules>
|
||||
17
app/src/test/java/com/legacy/android/ExampleUnitTest.kt
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.legacy.android
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||