diff --git a/.gitignore b/.gitignore
index b1123a0..66d9d70 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,4 @@
.externalNativeBuild
.cxx
/.idea/
+/ttutils/jniLibs/
diff --git a/app/build.gradle b/app/build.gradle
index 192fd61..799e076 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -168,6 +168,8 @@ dependencies {
//工具类
implementation 'com.blankj:utilcodex:1.31.1'
+ //压缩文件解压
+ implementation 'org.zeroturnaround:zt-zip:1.15'
//动态权限框架
implementation 'com.github.getActivity:XXPermissions:18.6'
// 吐司框架:https://github.com/getActivity/Toaster
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9fd0ecd..528287b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,6 +3,12 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.example.mir4updater">
+
+
+
+
+
+
@@ -12,6 +18,7 @@
+
+
+
permissions, boolean allGranted) {
+ Log.e(TAG, "onGranted: permissions = " + permissions + " allGranted = " + allGranted);
+ if (!allGranted) {
+// Toaster.show("获取部分权限成功,但部分权限未正常授予");
+ return;
+ }
+ Log.e(TAG, "onGranted: 获取权限成功");
+ installXapk();
+ }
+
+ @Override
+ public void onDenied(@NonNull List permissions, boolean doNotAskAgain) {
+ Log.e(TAG, "onDenied: permissions = " + permissions + " doNotAskAgain = " + doNotAskAgain);
+ if (doNotAskAgain) {
+ Toaster.show("被永久拒绝授权,请手动授予权限");
+ // 如果是被永久拒绝就跳转到应用权限系统设置页面
+ XXPermissions.startPermissionActivity(MainActivity.this, permissions);
+ } else {
+ Log.e(TAG, "onGranted: 获取权限失败");
+ }
+ }
+ });
+ }
+
+ private void installXapk() {
+ File file = new File("/storage/emulated/0/Download/Google Chrome_135.0.7049.100_APKPure.xapk");
+ XapkUtils.getInstance().installXapk(file.getAbsolutePath());
+ }
+
+ private void requestPackageInstalls() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
+ !getPackageManager().canRequestPackageInstalls()) {
+ Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES)
+ .setData(Uri.parse("package:" + getPackageName()));
+ startActivityForResult(intent, REQUEST_INSTALL_CODE);
+ } else {
+ getInstallPermission();
+ }
+ }
+
public class BtnClick {
public void upload(View view) {
startActivity(new Intent(MainActivity.this, UploadActivity.class));
@@ -490,5 +547,9 @@ public class MainActivity extends BaseMvvmActivity() {
@Override
@@ -65,7 +66,7 @@ public class MainViewModel extends BaseViewModel ks = extras.keySet();
+//// Iterator iterator = ks.iterator();
+//// while (iterator.hasNext()) {
+//// Log.d("KEY", iterator.next());
+//// }
+// String STATUS = intent.getStringExtra(PackageInstaller.EXTRA_STATUS);
+// String PACKAGE_NAME = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME);
+// String SESSION_ID = intent.getStringExtra(PackageInstaller.EXTRA_SESSION_ID);
+// String STATUS_MESSAGE = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
+// String LEGACY_STATUS = intent.getStringExtra("android.content.pm.extra.LEGACY_STATUS");
+//
+// Log.e(TAG, "EXTRA_STATUS: " + STATUS);
+// Log.e(TAG, "PACKAGE_NAME: " + PACKAGE_NAME);
+// Log.e(TAG, "SESSION_ID: " + SESSION_ID);
+// Log.e(TAG, "STATUS_MESSAGE: " + LEGACY_STATUS);
+// Log.e(TAG, "LEGACY_STATUS: " + STATUS_MESSAGE);
+//
+// if ("INSTALL_SUCCEEDED".equals(STATUS_MESSAGE)) {
+// Toaster.show(PACKAGE_NAME + "安装成功");
+// }
+// }
+}
diff --git a/app/src/main/java/com/example/mir4updater/utils/ApkUtils.java b/app/src/main/java/com/example/mir4updater/utils/ApkUtils.java
index 85e4fd2..a5f40a5 100644
--- a/app/src/main/java/com/example/mir4updater/utils/ApkUtils.java
+++ b/app/src/main/java/com/example/mir4updater/utils/ApkUtils.java
@@ -87,4 +87,6 @@ public class ApkUtils {
}
return false;
}
+
+
}
diff --git a/app/src/main/java/com/example/mir4updater/utils/FileUtils.java b/app/src/main/java/com/example/mir4updater/utils/FileUtils.java
new file mode 100644
index 0000000..2573c1c
--- /dev/null
+++ b/app/src/main/java/com/example/mir4updater/utils/FileUtils.java
@@ -0,0 +1,12 @@
+package com.example.mir4updater.utils;
+
+import java.io.File;
+
+public class FileUtils {
+ public static String getFileNoExName(String filePath) {
+
+ String fileNameEx = filePath.substring(filePath.lastIndexOf(File.separator) + 1);
+ String fileName = fileNameEx.substring(0,fileNameEx.lastIndexOf("."));
+ return fileName;
+ }
+}
diff --git a/app/src/main/java/com/example/mir4updater/utils/JsonUtils.java b/app/src/main/java/com/example/mir4updater/utils/JsonUtils.java
new file mode 100644
index 0000000..151f59e
--- /dev/null
+++ b/app/src/main/java/com/example/mir4updater/utils/JsonUtils.java
@@ -0,0 +1,39 @@
+package com.example.mir4updater.utils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+
+public class JsonUtils {
+ /**
+ * 读取json文件,返回json串
+ * @param fileName
+ * @return
+ */
+ public static String readJsonFile(String fileName) {
+ String jsonStr = "";
+ try {
+ File jsonFile = new File(fileName);
+ FileReader fileReader = new FileReader(jsonFile);
+
+ Reader reader = new InputStreamReader(new FileInputStream(jsonFile), StandardCharsets.UTF_8);
+ int ch = 0;
+ StringBuffer sb = new StringBuffer();
+ while ((ch = reader.read()) != -1) {
+ sb.append((char) ch);
+ }
+ fileReader.close();
+ reader.close();
+ jsonStr = sb.toString();
+ return jsonStr;
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/example/mir4updater/utils/PermissionUtils.java b/app/src/main/java/com/example/mir4updater/utils/PermissionUtils.java
index 0af48c5..74182c2 100644
--- a/app/src/main/java/com/example/mir4updater/utils/PermissionUtils.java
+++ b/app/src/main/java/com/example/mir4updater/utils/PermissionUtils.java
@@ -16,6 +16,10 @@ public class PermissionUtils {
Permission.MANAGE_EXTERNAL_STORAGE,
};
+ public static final String[] Install_Permission = new String[]{
+ Permission.REQUEST_INSTALL_PACKAGES,
+ };
+
public static boolean checkAppData(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 请求Android/data目录的权限,Android/obb目录则把data替换成obb即可。
diff --git a/app/src/main/java/com/example/mir4updater/utils/XapkUtils.java b/app/src/main/java/com/example/mir4updater/utils/XapkUtils.java
new file mode 100644
index 0000000..8ed8d18
--- /dev/null
+++ b/app/src/main/java/com/example/mir4updater/utils/XapkUtils.java
@@ -0,0 +1,480 @@
+package com.example.mir4updater.utils;
+
+import android.annotation.SuppressLint;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInstaller;
+import android.os.Build;
+import android.os.Environment;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.RequiresApi;
+
+import com.example.mir4updater.bean.Expansions;
+import com.example.mir4updater.bean.SplitApks;
+import com.example.mir4updater.receiver.InstallResultReceiver;
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.reflect.TypeToken;
+import com.hjq.toast.Toaster;
+
+import org.zeroturnaround.zip.ZipUtil;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Type;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.core.ObservableEmitter;
+import io.reactivex.rxjava3.core.ObservableOnSubscribe;
+import io.reactivex.rxjava3.core.Observer;
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.schedulers.Schedulers;
+
+public class XapkUtils {
+ private static final String TAG = "XapkUtils";
+
+ @SuppressLint("StaticFieldLeak")
+ private static XapkUtils sInstance;
+ private Context mContext;
+
+ private XapkUtils(Context context) {
+ this.mContext = context;
+ }
+
+ // TODO: 2025/4/18 优化为单例模式
+ public static void init(Context context) {
+ if (sInstance == null) {
+ sInstance = new XapkUtils(context);
+ }
+ }
+
+ public static XapkUtils getInstance() {
+ if (sInstance == null) {
+ throw new IllegalStateException("You must be init XapkUtils first");
+ }
+ return sInstance;
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ public void installXapk(String filePath) {
+ File file = new File(filePath);
+ if (file.exists() && file.isFile()) {
+ upzipXapk(file);
+ } else {
+ Log.e(TAG, "installXAPK: " + "File not exists");
+
+ }
+ }
+
+ @Deprecated
+ private static String unpackPath;
+ @Deprecated
+ private static long XapkSize;
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ private void upzipXapk(File XapkFile) {
+ Observable.create(new ObservableOnSubscribe() {
+ @Override
+ public void subscribe(ObservableEmitter emitter) throws Exception {
+ if (ZipUtil.containsEntry(XapkFile, "manifest.json")) {
+ //判断json文件是否存在
+ String dirName = FileUtils.getFileNoExName(XapkFile.getAbsolutePath());
+ unpackPath = XapkFile.getParentFile().getAbsolutePath() + File.separator + dirName;
+ File unpackDir = new File(unpackPath);
+ ZipUtil.unpack(XapkFile, unpackDir);
+ String jsonString = JsonUtils.readJsonFile(unpackDir.getAbsolutePath() + File.separator + "manifest.json");
+ emitter.onNext(jsonString);
+ } else {
+ Toaster.show("读取XAPK配置文件失败");
+ emitter.onComplete();
+ }
+ }
+ }).subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(new Observer() {
+ @Override
+ public void onSubscribe(Disposable d) {
+
+ }
+
+ @Override
+ public void onNext(String s) {
+ JsonObject jsonObject = JsonParser.parseString(s).getAsJsonObject();
+ XapkSize = jsonObject.get("total_size").getAsLong();
+ JsonElement split_configs_em = jsonObject.get("split_configs");
+ //获取分割的配置
+ JsonElement expansions_em = jsonObject.get("expansions");
+ //获取OBB文件配置
+ JsonElement split_apks_em = jsonObject.get("split_apks");
+ readConfig(split_configs_em, expansions_em, split_apks_em);
+// Log.e(TAG, "installXAPK: " + jsonString);
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ Log.e(TAG, "onError: " + e.getMessage());
+ }
+
+ @Override
+ public void onComplete() {
+ Log.e(TAG, "onComplete: ");
+ }
+ });
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ private void readConfig(JsonElement split_configs, JsonElement expansions, JsonElement split_apks) {
+ //没有split_configs只有单个apk,读取split_apks 的base字段的file文件
+ //没有expansions 没有obb文件
+ //split_apks应该是都会有的,包含一个base的json对象
+ if (null != split_configs) {
+ getSplitConfigs(split_configs.getAsJsonArray());
+ } else {
+ Log.e(TAG, "readConfig: " + "not found split_configs json data");
+ }
+
+ if (null != expansions) {
+ readOBBConfig(expansions.getAsJsonArray());
+ } else {
+ Log.e(TAG, "readConfig: " + "not found expansions json data");
+ }
+
+ if (null != split_apks) {
+ getSplitApks(split_apks.getAsJsonArray());
+ } else {
+ Log.e(TAG, "readConfig: " + "not found split_apks json data");
+ }
+ }
+
+ List configList = new ArrayList<>();
+
+ /**
+ * 获取分裂的配置文件
+ *
+ * @param jsonArray
+ */
+ private void getSplitConfigs(JsonArray jsonArray) {
+ Type type = new TypeToken>() {
+ }.getType();
+ Gson gson = new Gson();
+ configList = gson.fromJson(gson.toJson(jsonArray), type);
+// StringBuilder configStringBuilder = new StringBuilder();
+// for (String config : configList) {
+// if (configStringBuilder.length() > 0) {
+// configStringBuilder.append(",");
+// }
+// configStringBuilder.append(config);
+// }
+// Log.e(TAG, "getSplitConfigs: " + configStringBuilder.toString());
+ }
+
+ /**
+ * 获取obb配置文件
+ *
+ * @param jsonArray
+ */
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ private void readOBBConfig(JsonArray jsonArray) {
+ if (TextUtils.isEmpty(unpackPath)) {
+ Log.e(TAG, "readOBBConfig: " + "unpack directory is empty");
+ return;
+ }
+ Type type = new TypeToken>() {
+ }.getType();
+ Gson gson = new Gson();
+ List expansionsList = gson.fromJson(gson.toJson(jsonArray), type);
+ if (null != expansionsList && expansionsList.size() > 0) {
+ for (Expansions expansions : expansionsList) {
+ if (copyObbFile(expansions)) {
+ Log.e(TAG, "readOBBConfig: " + "success");
+ } else {
+ Log.e(TAG, "readOBBConfig: " + "copy oob File failure");
+ }
+ }
+ }
+ }
+
+ /**
+ * 获取分裂的apk文件
+ *
+ * @param jsonArray
+ */
+ private void getSplitApks(JsonArray jsonArray) {
+ Type type = new TypeToken>() {
+ }.getType();
+ Gson gson = new Gson();
+ List splitApksList = gson.fromJson(gson.toJson(jsonArray), type);
+ getApkFilePath(configList, splitApksList);
+// if (null != splitApksList && splitApksList.size() > 0) {
+// for (SplitApks splitApks : splitApksList) {
+//
+// }
+// }
+ }
+
+ /**
+ * @param configList
+ * @param apkList configList 可以为空,为空解析split_apks对象中的base
+ * apkList应该不会为空
+ */
+ private void getApkFilePath(List configList, List apkList) {
+ List filePath = new ArrayList<>();
+ //应该直接获取SplitApks里面的file
+ if (null != configList && configList.size() > 0) {
+ //split_configs不为空的情况
+ filePath.add(unpackPath + File.separator + apkList.get(getFileFromId(apkList, "base")).getFile());
+ for (String config : configList) {
+ int position = getFileFromId(apkList, config);
+ if (position != -1) {
+ String file = apkList.get(position).getFile();
+ filePath.add(unpackPath + File.separator + file);
+ }
+ }
+// Log.e(TAG, "installxApk: " + filePath.toString());
+ } else {
+ //split_configs为空的情况
+ int position = getFileFromId(apkList, "base");
+ if (position != -1) {
+ String file = apkList.get(position).getFile();
+ filePath.add(file);
+ Log.e(TAG, "installxApk: " + "base file = " + file);
+ }
+ }
+
+ if (filePath.size() != 0) {
+ installApk(filePath);
+ }
+ }
+
+ /**
+ * 通过id获取文件
+ *
+ * @param splitApks
+ * @param id
+ */
+ private int getFileFromId(List splitApks, String id) {
+ int position = -1;
+ for (int i = 0; i < splitApks.size(); i++) {
+ if (splitApks.get(i).getId().equals(id)) {
+ position = i;
+ }
+ }
+ return position;
+ }
+
+ private void installApk(final List paths) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ installAppatPie(paths);
+ }
+ }).start();
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ public void installAppatPie(List apkFilePath) {
+ Log.e(TAG, "installApps: 正在安装应用 " + apkFilePath);
+// File file = new File(apkFilePath);
+ PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller();
+ PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(PackageInstaller
+ .SessionParams.MODE_FULL_INSTALL);
+ sessionParams.setSize(XapkSize);
+ int sessionId = createSession(packageInstaller, sessionParams);
+ if (sessionId != -1) {
+ for (String apkPath : apkFilePath) {
+ copyApkFile(packageInstaller, sessionId, apkPath);
+ }
+// boolean copySuccess = ;
+// if (copySuccess) {
+ Toaster.show("正在安装应用");
+ install(packageInstaller, sessionId, mContext);
+// }
+//
+ }
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ private static void install(PackageInstaller packageInstaller, int sessionId, Context context) {
+ try {
+ PackageInstaller.Session session = packageInstaller.openSession(sessionId);
+ Intent intent = new Intent(context, InstallResultReceiver.class);
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ context,
+ 1, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT
+ );
+ session.commit(pendingIntent.getIntentSender());
+ } catch (IOException e) {
+ e.printStackTrace();
+ Log.e(TAG, "install: " + e.getMessage());
+ }
+ }
+
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ private static int createSession(PackageInstaller packageInstaller, PackageInstaller.SessionParams sessionParams) {
+ int sessionId = -1;
+ try {
+ sessionId = packageInstaller.createSession(sessionParams);
+ } catch (IOException e) {
+ e.printStackTrace();
+ Log.e(TAG, "createSession: " + e.getMessage());
+ }
+ return sessionId;
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ private static boolean copyApkFile(PackageInstaller pi, int sessionId, String apkFilePath) {
+ boolean success = false;
+ File apkFile = new File(apkFilePath);
+ PackageInstaller.Session session = null;
+ try {
+ session = pi.openSession(sessionId);
+ OutputStream out = session.openWrite(FileUtils.getFileNoExName(apkFilePath), 0, apkFile.length());
+ FileInputStream input = new FileInputStream(apkFile);
+ int read = 0;
+ byte[] buffer = new byte[65536];
+// while (read != -1) {
+// read = input.read(buffer);
+// out.write(buffer, 0, read);
+// }
+ while (true) {
+ read = input.read(buffer);
+ if (read == -1) {
+ session.fsync(out);
+ success = true;
+ out.close();
+ input.close();
+ break;
+ }
+ out.write(buffer, 0, read);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ Log.e("fht", "copyApkFile" + e.getMessage());
+ }
+ Log.e("fht", "copyApkFile" + "success = " + success);
+ return success;
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ private boolean copyObbFile(Expansions expansions) {
+ String install_location = expansions.getInstall_location();
+ //install_location 不清楚是否还有其他定义
+
+ String file = expansions.getFile();
+ String install_path = expansions.getInstall_path();
+
+ if (TextUtils.isEmpty(file)) {
+ Log.e(TAG, "copyObbFile: " + "file path is empty");
+ return false;
+ } else {
+ File localFile = new File(unpackPath + File.separator + file);
+ if (localFile.exists() && localFile.isFile()) {
+ File installFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + install_path);
+ Log.e(TAG, "copyObbFile: " + "localFile: " + localFile.getAbsolutePath());
+ Log.e(TAG, "copyObbFile: " + "installFile: " + installFile.getAbsolutePath());
+ try {
+ Path path = Paths.get(localFile.getAbsolutePath());
+ Files.copy(path, new FileOutputStream(installFile));
+ return true;
+ } catch (IOException e) {
+ Log.e(TAG, "copyObbFile: " + "IOException" + e.getMessage());
+ e.printStackTrace();
+ return false;
+ }
+ } else {
+ Log.e(TAG, "copyObbFile: " + "localFile: " + "File not exists");
+ return false;
+ }
+
+ }
+ }
+
+
+
+
+
+ @Deprecated
+ public static File unzipXapk(Context context, File xapkFile) throws IOException {
+ File outputDir = new File(context.getExternalCacheDir(), "xapk_unzip");
+ if (!outputDir.exists()) outputDir.mkdirs();
+
+ try (ZipFile zipFile = new ZipFile(xapkFile)) {
+ Enumeration extends ZipEntry> entries = zipFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ File entryFile = new File(outputDir, entry.getName());
+ if (entry.isDirectory()) {
+ entryFile.mkdirs();
+ } else {
+ try (InputStream in = zipFile.getInputStream(entry);
+ OutputStream out = new FileOutputStream(entryFile)) {
+ byte[] buffer = new byte[4096];
+ int len;
+ while ((len = in.read(buffer)) > 0) {
+ out.write(buffer, 0, len);
+ }
+ }
+ }
+ }
+ }
+ return outputDir;
+ }
+
+ @Deprecated
+ public static void installXapk(Context context, File outputDir) {
+ PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
+ PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL
+ );
+
+ try {
+ int sessionId = packageInstaller.createSession(params);
+ PackageInstaller.Session session = packageInstaller.openSession(sessionId);
+
+ File[] apkFiles = outputDir.listFiles(file -> file.getName().endsWith(".apk"));
+ if (apkFiles == null) return;
+
+ for (File apkFile : apkFiles) {
+ try (InputStream in = new FileInputStream(apkFile);
+ OutputStream out = session.openWrite(apkFile.getName(), 0, apkFile.length())) {
+ byte[] buffer = new byte[65536];
+ int bytesRead;
+ while ((bytesRead = in.read(buffer)) != -1) {
+ out.write(buffer, 0, bytesRead);
+ }
+ session.fsync(out);
+ }
+ }
+
+ // 提交安装请求
+ Intent callbackIntent = new Intent(context, InstallResultReceiver.class);
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ context, sessionId, callbackIntent, PendingIntent.FLAG_IMMUTABLE
+ );
+ session.commit(pendingIntent.getIntentSender());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index d7de177..a40eb2a 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -133,6 +133,23 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_app_storage_state" />
+
+