This commit is contained in:
世界 2020-04-28 23:04:50 +08:00
parent 7d39be9cc0
commit c19a079ca7
No known key found for this signature in database
GPG Key ID: CD109927C34A63C4
16 changed files with 408 additions and 51 deletions

View File

@ -9,13 +9,13 @@ jobs:
fossBuild:
name: Foss Build
runs-on: ubuntu-latest
if: "contains(github.event.head_commit.message, '[RELEASE]') || contains(github.event.head_commit.message, '[N]')"
if: "contains(github.event.head_commit.message, '[RELEASE]')"
steps:
- uses: actions/checkout@v2
- uses: actions/cache@v1
with:
path: ~/.gradle
key: gradle-${{ hashFiles('**/*.gradle') }}
key: native-${{ hashFiles('**/*.gradle') }}
restore-keys: |
${{ runner.os }}-gradle-
- run: git submodule update --init --recursive
@ -73,7 +73,7 @@ jobs:
./patch_boringssl.sh
[ -d "patch_boringssl/build" ] || ./build_boringssl.sh
[ -d "boringssl/build" ] || ./build_boringssl.sh
- name: assemble
run: |

75
.github/workflows/native.yml vendored Normal file
View File

@ -0,0 +1,75 @@
name: Native Build
on:
push:
branches:
- master
jobs:
nativeBuild:
name: Native Build
runs-on: ubuntu-latest
if: "contains(github.event.head_commit.message, '[N]')"
steps:
- uses: actions/checkout@v2
- uses: actions/cache@v1
with:
path: ~/.gradle
key: native-${{ hashFiles('**/*.gradle') }}
restore-keys: |
${{ runner.os }}-gradle-
- run: git submodule update --init --recursive
- uses: actions/cache@v1
with:
path: TMessagesProj/jni/boringssl/build
key: boringssl-${{ hashFiles('TMessagesProj/jni/boringssl/.git') }}
- uses: actions/cache@v1
with:
path: TMessagesProj/jni/ffmpeg/build
key: ffmpeg-${{ hashFiles('TMessagesProj/jni/ffmpeg/.git') }}
- uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build native libraries
run: |
cd TMessagesProj/jni
while :
do
sudo apt-get install -y ninja-build && break
sleep 5
done
export NDK=$ANDROID_HOME/ndk-bundle
export NINJA_PATH=/usr/bin/ninja
export PATH=`echo $ANDROID_HOME/cmake/*/bin`:$PATH
[ -d "ffmpeg/build" ] || ./build_ffmpeg_clang.sh
./patch_ffmpeg.sh
./patch_boringssl.sh
[ -d "boringssl/build" ] || ./build_boringssl.sh
- name: assemble
run: |
sudo bash <<EOF
export LOCAL_PROPERTIES="${{ secrets.LOCAL_PROPERTIES }}" &&
./gradlew assembleAfatFoss
EOF
ls TMessagesProj/build/outputs/apk
echo ::set-env name=APK_FILE::$(find TMessagesProj/build/outputs/apk -name "*universal*")
- uses: actions/upload-artifact@master
with:
name: NekoX-Foss
path: ${{ env.APK_FILE }}
- uses: actions/upload-artifact@master
with:
name: Boringssl Library
path: "TMessagesProj/jni/boringssl/build"
- uses: actions/upload-artifact@master
with:
name: Ffmpeg Library
path: "TMessagesProj/jni/ffmpeg/build"

View File

@ -111,6 +111,7 @@ void Handshake::cleanupHandshake() {
void Handshake::clearServerPublicKey() {
serverPublicKeys.clear();
clearServerPublicKey();
}
@ -480,6 +481,8 @@ void Handshake::processHandshakeResponse(TLObject *message, int64_t messageId) {
return;
}
if (LOGS_ENABLED) DEBUG_D("publicKey: %s, fingerprint: %lld",key.c_str(),keyFingerprint);
authServerNonce = new ByteArray(result->server_nonce.get());
uint64_t pq = ((uint64_t) (result->pq->bytes[0] & 0xff) << 56) |

View File

@ -33,13 +33,17 @@ import org.telegram.tgnet.TLRPC;
import org.telegram.ui.Components.ForegroundDetector;
import java.io.File;
import java.lang.reflect.Method;
import tw.nekomimi.nekogram.ExternalGcm;
import tw.nekomimi.nekogram.NekoConfig;
import tw.nekomimi.nekogram.utils.EnvUtil;
import tw.nekomimi.nekogram.utils.FileUtil;
import tw.nekomimi.nekogram.utils.ProxyUtil;
import tw.nekomimi.nekogram.utils.UIUtil;
import static android.os.Build.VERSION.SDK_INT;
public class ApplicationLoader extends Application {
@SuppressLint("StaticFieldLeak")
@ -62,12 +66,103 @@ public class ApplicationLoader extends Application {
@Override
protected void attachBaseContext(Context base) {
if (SDK_INT >= Build.VERSION_CODES.P) {
Reflection.unseal(base);
}
super.attachBaseContext(base);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
if (SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
MultiDex.install(this);
}
}
/**
* @author weishu
* @date 2018/6/7.
*/
public static class Reflection {
private static final String TAG = "Reflection";
private static Object sVmRuntime;
private static Method setHiddenApiExemptions;
static {
if (SDK_INT >= Build.VERSION_CODES.P) {
try {
Method forName = Class.class.getDeclaredMethod("forName", String.class);
Method getDeclaredMethod = Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class);
Class<?> vmRuntimeClass = (Class<?>) forName.invoke(null, "dalvik.system.VMRuntime");
Method getRuntime = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null);
setHiddenApiExemptions = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", new Class[]{String[].class});
sVmRuntime = getRuntime.invoke(null);
} catch (Throwable e) {
FileLog.e("reflect bootstrap failed:", e);
}
}
}
private static int UNKNOWN = -9999;
private static final int ERROR_SET_APPLICATION_FAILED = -20;
private static final int ERROR_EXEMPT_FAILED = -21;
private static int unsealed = UNKNOWN;
public static int unseal(Context context) {
if (SDK_INT < 28) {
// Below Android P, ignore
return 0;
}
// try exempt API first.
if (exemptAll()) {
return 0;
} else {
return ERROR_EXEMPT_FAILED;
}
}
/**
* make the method exempted from hidden API check.
*
* @param method the method signature prefix.
* @return true if success.
*/
public static boolean exempt(String method) {
return exempt(new String[]{method});
}
/**
* make specific methods exempted from hidden API check.
*
* @param methods the method signature prefix, such as "Ldalvik/system", "Landroid" or even "L"
* @return true if success
*/
public static boolean exempt(String... methods) {
if (sVmRuntime == null || setHiddenApiExemptions == null) {
return false;
}
try {
setHiddenApiExemptions.invoke(sVmRuntime, new Object[]{methods});
return true;
} catch (Throwable e) {
return false;
}
}
/**
* Make all hidden API exempted.
*
* @return true if success.
*/
public static boolean exemptAll() {
return exempt(new String[]{"L"});
}
}
@SuppressLint("SdCardPath")
public static File getDataDirFixed() {
try {
@ -220,11 +315,22 @@ public class ApplicationLoader extends Application {
applicationHandler = new Handler(applicationContext.getMainLooper());
startPushService();
try {
EnvUtil.doTest();
}catch (Exception e) {
FileLog.e("EnvUtil test Failed",e);
}
}
public static void startPushService() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
if (SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
return; // USE NOTIF LISTENER
}
SharedPreferences preferences = MessagesController.getGlobalNotificationsSettings();
@ -236,7 +342,7 @@ public class ApplicationLoader extends Application {
}
if (enabled) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && NekoConfig.residentNotification) {
if (SDK_INT >= Build.VERSION_CODES.O && NekoConfig.residentNotification) {
applicationContext.startForegroundService(new Intent(applicationContext, NotificationsService.class));
} else {
applicationContext.startService(new Intent(applicationContext, NotificationsService.class));

View File

@ -112,10 +112,8 @@ import org.telegram.ui.Components.SlideView;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.security.KeyFactory;
import java.math.BigInteger;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -134,6 +132,7 @@ import tw.nekomimi.nekogram.BottomBuilder;
import tw.nekomimi.nekogram.DataCenter;
import tw.nekomimi.nekogram.EditTextAutoFill;
import tw.nekomimi.nekogram.NekoXConfig;
import tw.nekomimi.nekogram.parts.PKCS1Pub;
import tw.nekomimi.nekogram.utils.AlertUtil;
@SuppressLint("HardwareIds")
@ -410,7 +409,7 @@ public class LoginActivity extends BaseFragment implements NotificationCenter.No
});
builder.addRadioItem(LocaleController.getString("CustomApiOffical", R.string.CustomApiOffical), NekoXConfig.customApi == 1, (cell) -> {
builder.addRadioItem(LocaleController.getString("CustomApiOfficial", R.string.CustomApiOfficial), NekoXConfig.customApi == 1, (cell) -> {
targetApi.set(1);
@ -543,9 +542,9 @@ public class LoginActivity extends BaseFragment implements NotificationCenter.No
EditText[] inputs = new EditText[6];
builder.addTitle("Custom Backend",
builder.addTitle(LocaleController.getString("CustomBackend",R.string.CustomBackend),
true,
"~");
LocaleController.getString("CustomBackendNotice",R.string.CustomBackendNotice));
int dcType;
@ -557,7 +556,7 @@ public class LoginActivity extends BaseFragment implements NotificationCenter.No
dcType = 0;
}
builder.addRadioItem("OFFICAL", dcType == 0, (cell) -> {
builder.addRadioItem(LocaleController.getString("CustomBackendProduction",R.string.CustomBackendProduction), dcType == 0, (cell) -> {
targetDc.set(0);
@ -569,7 +568,7 @@ public class LoginActivity extends BaseFragment implements NotificationCenter.No
});
builder.addRadioItem("TEST DC", dcType == 1, (cell) -> {
builder.addRadioItem(LocaleController.getString("CustomBackendTestDC",R.string.CustomBackendTestDC), dcType == 1, (cell) -> {
targetDc.set(1);
@ -593,7 +592,7 @@ public class LoginActivity extends BaseFragment implements NotificationCenter.No
});
inputs[0] = builder.addEditText("Ipv4 Address");
inputs[0] = builder.addEditText(LocaleController.getString("CustomBackendIpv4",R.string.CustomBackendIpv4));
inputs[0].setFilters(new InputFilter[]{new InputFilter.LengthFilter(15)});
if (StrUtil.isNotBlank(NekoXConfig.customDcIpv4)) {
inputs[0].setText(NekoXConfig.customDcIpv4);
@ -618,7 +617,7 @@ public class LoginActivity extends BaseFragment implements NotificationCenter.No
}
});
inputs[1] = builder.addEditText("Ipv6 Address");
inputs[1] = builder.addEditText(LocaleController.getString("CustomBackendIpv6",R.string.CustomBackendIpv6));
if (StrUtil.isNotBlank(NekoXConfig.customDcIpv6)) {
inputs[1].setText(NekoXConfig.customDcIpv6);
}
@ -642,7 +641,7 @@ public class LoginActivity extends BaseFragment implements NotificationCenter.No
}
});
inputs[2] = builder.addEditText("Port");
inputs[2] = builder.addEditText(LocaleController.getString("UseProxyPort",R.string.UseProxyPort));
inputs[2].setInputType(InputType.TYPE_CLASS_NUMBER);
if (NekoXConfig.customDcPort != 0) {
inputs[2].setText(NekoXConfig.customDcPort + "");
@ -673,7 +672,7 @@ public class LoginActivity extends BaseFragment implements NotificationCenter.No
}
});
inputs[3] = builder.addEditText("Layer");
inputs[3] = builder.addEditText(LocaleController.getString("CustomBackendLayer",R.string.CustomBackendLayer));
inputs[3].setInputType(InputType.TYPE_CLASS_NUMBER);
if (NekoXConfig.customDcLayer != 0) {
inputs[3].setText(NekoXConfig.customDcLayer + "");
@ -704,7 +703,7 @@ public class LoginActivity extends BaseFragment implements NotificationCenter.No
}
});
inputs[4] = builder.addEditText("Public Key");
inputs[4] = builder.addEditText(LocaleController.getString("CustomBackendPublicKey",R.string.CustomBackendPublicKey));
inputs[4].setInputType(InputType.TYPE_CLASS_TEXT);
inputs[4].setGravity(Gravity.TOP | LocaleController.generateFlagStart());
inputs[4].setSingleLine(false);
@ -721,32 +720,23 @@ public class LoginActivity extends BaseFragment implements NotificationCenter.No
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (StrUtil.isBlank(s.toString())) {
NekoXConfig.customDcPublicKey = "";
inputs[5].setText("PublicKey required");
inputs[5].setText("");
} else {
try {
String publicKeyBase64 = s.toString()
.replace("-----BEGIN RSA PUBLIC KEY-----", "")
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END RSA PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "");
.replace("-----END RSA PUBLIC KEY-----", "");
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(Base64.decode(publicKeyBase64)));
PKCS1Pub.decodePKCS1PublicKey(Base64.decode(publicKeyBase64));
NekoXConfig.customDcPublicKey = s.toString();
inputs[4].setError(null);
long fingerprint = DataCenter.calcAuthKeyId(publicKey.getEncoded());
inputs[5].setText(Long.toHexString(fingerprint));
NekoXConfig.customDcFingerprint = fingerprint;
} catch (Exception e) {
FileLog.e("Invalid public key",e);
inputs[4].setError("Invalid RSA Public Key");
inputs[5].setText("PublicKey required");
inputs[4].setError("Invalid PKCS1 Key");
NekoXConfig.customDcPublicKey = "";
}
@ -759,12 +749,41 @@ public class LoginActivity extends BaseFragment implements NotificationCenter.No
}
});
inputs[5] = builder.addEditText();
inputs[5].setText("PublicKey required");
inputs[5].setEnabled(false);
inputs[5] = builder.addEditText(LocaleController.getString("CustomBackendFingerprint",R.string.CustomBackendFingerprint));
if (NekoXConfig.customDcFingerprint != 0) {
inputs[5].setText(NekoXConfig.customDcFingerprint + "");
inputs[5].setText("0x" + Long.toString(NekoXConfig.customDcFingerprint,16));
}
inputs[5].addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (StrUtil.isBlank(s.toString())) {
NekoXConfig.customDcFingerprint = 0;
inputs[5].setError(null);
} else {
String f = s.toString();
int r = 10;
if (f.startsWith("0x")) {
f = f.substring(2);
r = 16;
}
try {
NekoXConfig.customDcFingerprint = new BigInteger(f,r).longValue();
inputs[5].setError(null);
} catch (NumberFormatException e) {
NekoXConfig.customDcFingerprint = 0;
inputs[5].setError("Invalid Fingerprint");
}
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
if (dcType < 2) {
@ -795,7 +814,7 @@ public class LoginActivity extends BaseFragment implements NotificationCenter.No
return Unit.INSTANCE;
} else if (StrUtil.isBlank(inputs[2].getText().toString())) {
} else if (NekoXConfig.customDcPort == 0) {
AlertUtil.showToast("Input Port");
inputs[2].setError("Port required");
@ -804,7 +823,7 @@ public class LoginActivity extends BaseFragment implements NotificationCenter.No
return Unit.INSTANCE;
} else if (StrUtil.isBlank(inputs[3].getText().toString())) {
} else if (NekoXConfig.customDcLayer == 0) {
AlertUtil.showToast("Input Layer");
inputs[3].setError("Layer required");
@ -813,7 +832,7 @@ public class LoginActivity extends BaseFragment implements NotificationCenter.No
return Unit.INSTANCE;
} else if (StrUtil.isBlank(inputs[4].getText().toString())) {
} else if (StrUtil.isBlank(NekoXConfig.customDcPublicKey)) {
AlertUtil.showToast("Input PublicKey");
inputs[4].setError("PublicKey required");
@ -822,6 +841,15 @@ public class LoginActivity extends BaseFragment implements NotificationCenter.No
return Unit.INSTANCE;
} else if (NekoXConfig.customDcFingerprint == 0L) {
AlertUtil.showToast("Input Fingerprint");
inputs[5].setError("Fingerprint required");
inputs[5].requestFocus();
AndroidUtilities.showKeyboard(inputs[5]);
return Unit.INSTANCE;
} else if (StrUtil.isBlank(NekoXConfig.customDcIpv4) && StrUtil.isBlank(NekoXConfig.customDcIpv6)) {
AlertUtil.showToast("Input Address");

View File

@ -1,24 +1,29 @@
package tw.nekomimi.nekogram
import cn.hutool.core.util.ArrayUtil
import cn.hutool.crypto.digest.DigestUtil
import org.telegram.messenger.MessagesController
import org.telegram.tgnet.ConnectionsManager
import org.telegram.tgnet.SerializedData
import java.math.BigInteger
import java.nio.ByteBuffer
import java.security.interfaces.RSAPublicKey
object DataCenter {
// func calcAuthKeyId(keyData []byte) int64 {
// sha1 := Sha1Digest(keyData)
// // Lower 64 bits = 8 bytes of 20 byte SHA1 hash.
// return int64(binary.LittleEndian.Uint64(sha1[12:]))
// }
// sha1 := Sha1Digest(keyData)
// // Lower 64 bits = 8 bytes of 20 byte SHA1 hash.
// return int64(binary.LittleEndian.Uint64(sha1[12:]))
// }
@JvmStatic
fun calcAuthKeyId(publicKey: ByteArray): Long {
fun calcAuthKeyId(publicKey: RSAPublicKey): Long {
val sha1 = DigestUtil.sha1(publicKey)
val key = SerializedData()
return ByteBuffer.wrap(ArrayUtil.sub(sha1, 12, sha1.size)).long
key.writeByteArray(publicKey.modulus.toByteArray())
key.writeByteArray(publicKey.publicExponent.toByteArray())
return BigInteger(DigestUtil.sha1(key.toByteArray()).slice(12 until 20).toByteArray()).toLong()
}
@ -80,7 +85,7 @@ object DataCenter {
}
ConnectionsManager.native_saveDatacenters(account)
ConnectionsManager.native_setLayer(account,layer)
ConnectionsManager.native_setLayer(account, layer)
repeat(5) {

View File

@ -4,6 +4,7 @@ import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Environment;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.LocaleController;

View File

@ -644,7 +644,7 @@ public class NekoSettingsActivity extends BaseFragment {
disablePhotoSideActionRow = rowCount++;
hideKeyboardOnChatScrollRow = rowCount++;
rearVideoMessagesRow = rowCount++;
hideAllTabRow = NekoXConfig.developerMode ? rowCount++ : -1;
hideAllTabRow = rowCount ++;
mapPreviewRow = rowCount++;
stickerSizeRow = rowCount++;
messageMenuRow = rowCount++;

View File

@ -87,7 +87,7 @@ public class NekoXConfig {
public static String customDcIpv4 = preferences.getString("custom_dc_v4", "");
public static String customDcIpv6 = preferences.getString("custom_dc_v6", "");
public static int customDcPort = preferences.getInt("custom_dc_port", 12345);
public static int customDcPort = preferences.getInt("custom_dc_port", 0);
public static int customDcLayer = preferences.getInt("custom_dc_layer", 0);
public static String customDcPublicKey = preferences.getString("custom_dc_public_key", "");

View File

@ -0,0 +1,99 @@
package tw.nekomimi.nekogram.parts;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
public class PKCS1Pub {
private static final int SEQUENCE_TAG = 0x30;
private static final int BIT_STRING_TAG = 0x03;
private static final byte[] NO_UNUSED_BITS = new byte[] { 0x00 };
private static final byte[] RSA_ALGORITHM_IDENTIFIER_SEQUENCE =
{(byte) 0x30, (byte) 0x0d,
(byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x01,
(byte) 0x05, (byte) 0x00};
public static RSAPublicKey decodePKCS1PublicKey(byte[] pkcs1PublicKeyEncoding)
throws NoSuchAlgorithmException, InvalidKeySpecException
{
byte[] subjectPublicKeyInfo2 = createSubjectPublicKeyInfoEncoding(pkcs1PublicKeyEncoding);
KeyFactory rsaKeyFactory = KeyFactory.getInstance("RSA");
RSAPublicKey generatePublic = (RSAPublicKey) rsaKeyFactory.generatePublic(new X509EncodedKeySpec(subjectPublicKeyInfo2));
return generatePublic;
}
public static byte[] createSubjectPublicKeyInfoEncoding(byte[] pkcs1PublicKeyEncoding)
{
byte[] subjectPublicKeyBitString = createDEREncoding(BIT_STRING_TAG, concat(NO_UNUSED_BITS, pkcs1PublicKeyEncoding));
byte[] subjectPublicKeyInfoValue = concat(RSA_ALGORITHM_IDENTIFIER_SEQUENCE, subjectPublicKeyBitString);
byte[] subjectPublicKeyInfoSequence = createDEREncoding(SEQUENCE_TAG, subjectPublicKeyInfoValue);
return subjectPublicKeyInfoSequence;
}
private static byte[] concat(byte[] ... bas)
{
int len = 0;
for (int i = 0; i < bas.length; i++)
{
len += bas[i].length;
}
byte[] buf = new byte[len];
int off = 0;
for (int i = 0; i < bas.length; i++)
{
System.arraycopy(bas[i], 0, buf, off, bas[i].length);
off += bas[i].length;
}
return buf;
}
private static byte[] createDEREncoding(int tag, byte[] value)
{
if (tag < 0 || tag >= 0xFF)
{
throw new IllegalArgumentException("Currently only single byte tags supported");
}
byte[] lengthEncoding = createDERLengthEncoding(value.length);
int size = 1 + lengthEncoding.length + value.length;
byte[] derEncodingBuf = new byte[size];
int off = 0;
derEncodingBuf[off++] = (byte) tag;
System.arraycopy(lengthEncoding, 0, derEncodingBuf, off, lengthEncoding.length);
off += lengthEncoding.length;
System.arraycopy(value, 0, derEncodingBuf, off, value.length);
return derEncodingBuf;
}
private static byte[] createDERLengthEncoding(int size)
{
if (size <= 0x7F)
{
// single byte length encoding
return new byte[] { (byte) size };
}
else if (size <= 0xFF)
{
// double byte length encoding
return new byte[] { (byte) 0x81, (byte) size };
}
else if (size <= 0xFFFF)
{
// triple byte length encoding
return new byte[] { (byte) 0x82, (byte) (size >> Byte.SIZE), (byte) size };
}
throw new IllegalArgumentException("size too large, only up to 64KiB length encoding supported: " + size);
}
}

View File

@ -0,0 +1,30 @@
package tw.nekomimi.nekogram.utils
import android.content.Context
import android.os.storage.StorageManager
import org.telegram.messenger.ApplicationLoader
import org.telegram.messenger.FileLog
import java.io.File
object EnvUtil {
@JvmStatic
@Suppress("UNCHECKED_CAST")
val rootDirectories by lazy {
val mStorageManager = ApplicationLoader.applicationContext.getSystemService(Context.STORAGE_SERVICE) as StorageManager
(mStorageManager.javaClass.getMethod("getVolumePaths").invoke(mStorageManager) as Array<String>).map { File(it) }
}
@JvmStatic
fun doTest() {
FileLog.d("rootDirectories: ${rootDirectories.size}")
rootDirectories.forEach { FileLog.d(it.path) }
}
}

View File

@ -6,10 +6,20 @@
<string name="CustomApi">Custom API</string>
<string name="UseCustomApiNotice">Log in using the custom api, if you are unable to register or log in, this may help.\n\nNote: fcm will not work if you are using the release version.</string>
<string name="CustomApiNo">Don\'t use custom API</string>
<string name="CustomApiOffical">Telegram Android</string>
<string name="CustomApiOfficial">Telegram Android</string>
<string name="CustomApiTGX">Telegram Android X</string>
<string name="CustomApiInput">Manual input</string>
<string name="CustomBackend">Custom Backend</string>
<string name="CustomBackendNotice">This function is only provided for expert users, if you don\'t know what the following options represent, just ignore it.</string>
<string name="CustomBackendProduction">Official Production DataCenter</string>
<string name="CustomBackendTestDC">Official Test DataCenter</string>
<string name="CustomBackendIpv4">Ipv4 Address</string>
<string name="CustomBackendIpv6">Ipv6 Address</string>
<string name="CustomBackendLayer">Layer</string>
<string name="CustomBackendPublicKey">Public Key</string>
<string name="CustomBackendFingerprint">Key Fingerprint</string>
<string name="AllowFlashCall">Allow flash call</string>
<string name="ChangeTranslateProvider">Change Provider</string>