├── .gitignore ├── .idea ├── .name ├── codeStyles │ └── Project.xml ├── encodings.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ │ └── com │ │ │ └── modosa │ │ │ └── apkinstaller │ │ │ ├── activity │ │ │ ├── AboutActivity.java │ │ │ ├── AbstractInstallerActivity.java │ │ │ ├── Install1Activity.java │ │ │ ├── Install2Activity.java │ │ │ ├── Install3Activity.java │ │ │ ├── Install4Activity.java │ │ │ ├── Install5Activity.java │ │ │ ├── Install6Activity.java │ │ │ ├── LaunchAppActivity.java │ │ │ ├── MainUiActivity.java │ │ │ ├── ManageAllowSourceActivity.java │ │ │ ├── SettingsActivity.java │ │ │ └── StartMainUiActivity.java │ │ │ ├── base │ │ │ └── Application.java │ │ │ ├── fragment │ │ │ ├── DpmSettingsFragment.java │ │ │ ├── MainFragment.java │ │ │ └── SettingsFragment.java │ │ │ ├── receiver │ │ │ ├── AdminReceiver.java │ │ │ └── SecretCodeReceiver.java │ │ │ └── util │ │ │ ├── AppInfoUtil.java │ │ │ ├── FileSizeUtil.java │ │ │ ├── IOUtils.java │ │ │ ├── NotifyUtil.java │ │ │ ├── OpUtil.java │ │ │ ├── PackageInstallerUtil.java │ │ │ ├── PraseContentUtil.java │ │ │ ├── ResultUtil.java │ │ │ ├── ShellUtil.java │ │ │ ├── apksource │ │ │ ├── ApkSource.java │ │ │ └── DefaultApkSource.java │ │ │ ├── filedescriptor │ │ │ ├── FileDescriptor.java │ │ │ └── NormalFileDescriptor.java │ │ │ ├── installer │ │ │ ├── ApkSourceBuilder.java │ │ │ ├── QueuedInstallation.java │ │ │ ├── SAIPackageInstaller.java │ │ │ ├── ShellSAIPackageInstaller.java │ │ │ ├── rooted │ │ │ │ └── RootedSAIPackageInstaller.java │ │ │ └── shizuku │ │ │ │ └── ShizukuSAIPackageInstaller.java │ │ │ └── shell │ │ │ ├── Shell.java │ │ │ ├── ShizukuShell.java │ │ │ └── SuShell.java │ └── res │ │ ├── drawable │ │ ├── alertdialog_background.xml │ │ ├── alertdialog_background_transparent.xml │ │ ├── avatar_dadaewq_circle_middle.xml │ │ ├── ic_dpm_background.xml │ │ ├── ic_filter_vintage_black_24dp.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_pick.xml │ │ ├── ic_rate.xml │ │ ├── ic_root_background.xml │ │ ├── ic_settings.xml │ │ ├── ic_settings_black_24dp.xml │ │ ├── ic_transparent.xml │ │ ├── my_btn_selector_file_picker_rail.xml │ │ └── selector_text_press_background.xml │ │ ├── layout │ │ ├── activity_main_ui.xml │ │ ├── app_theme.xml │ │ ├── confirm_checkbox.xml │ │ ├── confirmprompt_doublecheckbox.xml │ │ ├── hideicon_checkbox.xml │ │ ├── install_by_another.xml │ │ ├── install_content_view.xml │ │ ├── manage_allowsource.xml │ │ ├── manage_allowsource_view.xml │ │ └── uninstall_content_view.xml │ │ ├── menu │ │ ├── menu_about.xml │ │ ├── menu_clear_allowource.xml │ │ └── menu_main_ui.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_dpm.xml │ │ ├── ic_dpm_round.xml │ │ ├── ic_launcher.xml │ │ ├── ic_launcher_round.xml │ │ ├── ic_root.xml │ │ └── ic_root_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_dpm.png │ │ ├── ic_dpm_foreground.png │ │ ├── ic_dpm_round.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ ├── ic_launcher_round.png │ │ ├── ic_root.png │ │ ├── ic_root_foreground.png │ │ └── ic_root_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_dpm.png │ │ ├── ic_dpm_foreground.png │ │ ├── ic_dpm_round.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ ├── ic_launcher_round.png │ │ ├── ic_root.png │ │ ├── ic_root_foreground.png │ │ └── ic_root_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_dpm.png │ │ ├── ic_dpm_foreground.png │ │ ├── ic_dpm_round.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ ├── ic_launcher_round.png │ │ ├── ic_root.png │ │ ├── ic_root_foreground.png │ │ └── ic_root_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_dpm.png │ │ ├── ic_dpm_foreground.png │ │ ├── ic_dpm_round.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ ├── ic_launcher_round.png │ │ ├── ic_root.png │ │ ├── ic_root_foreground.png │ │ └── ic_root_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_dark_mode_switch.png │ │ ├── ic_dpm.png │ │ ├── ic_dpm_foreground.png │ │ ├── ic_dpm_round.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ ├── ic_launcher_round.png │ │ ├── ic_root.png │ │ ├── ic_root_foreground.png │ │ └── ic_root_round.png │ │ ├── values-night │ │ └── colors.xml │ │ ├── values-zh │ │ ├── arrays.xml │ │ └── strings.xml │ │ ├── values │ │ ├── arrays.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ ├── app_delegation.xml │ │ ├── appcenter_backup_rule.xml │ │ ├── device_admin.xml │ │ ├── file_paths.xml │ │ ├── pref_dpm_settings.xml │ │ ├── pref_main_ui.xml │ │ └── pref_setings.xml │ └── miui │ └── res │ ├── values-zh │ └── strings.xml │ └── values │ └── strings.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Install-Lion -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 36 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 41 | 42 | 43 | 44 | 45 | 46 | 48 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Install-Lion 2 | 接入了 [IceBox SDK](https://github.com/heruoxin/IceBox-SDK)、[Delegated-Scopes-Manager](https://github.com/heruoxin/Delegated-Scopes-Manager)与 [Shizuku](https://github.com/RikkaApps/Shizuku)实现静默安装 3 | 4 | 安装狮-IceBox---支持Android6.0及以上,需要[冰箱](https://www.coolapk.com/apk/com.catchingnow.icebox)作为设备管理员 5 | 6 | 安装狮-DSM-----支持Android8.0及以上,需要[冰箱](https://www.coolapk.com/apk/com.catchingnow.icebox)或[小黑屋](https://www.coolapk.com/apk/web1n.stopapp)作为设备管理员 7 | 8 | 安装狮-Shizuku--支持Android6.0及以上,需要[Shizuku](https://www.coolapk.com/apk/moe.shizuku.privileged.api)服务正常运行 9 | 10 | 安装狮-Root-----支持Android5.1以以上,需要正常授予Root权限 11 | 12 | 安装狮-DPM-----支持Android6.0及以上,需要通过命令激活成为设备管理员 13 | 14 | ` adb shell dpm set-device-owner com.modosa.apkinstaller/.receiver.AdminReceiver ` 15 | 16 | ## Download 17 | [Get it on CoolApk](https://www.coolapk.com/apk/com.modosa.apkinstaller) 20 | 21 | ## Thanks 22 | 23 | 24 | [PokeInstaller](https://github.com/bavelee/PokeInstaller) 25 | 26 | [SAI](https://github.com/Aefyr/SAI) 27 | 28 | [FreezeYou](https://github.com/Playhi/FreezeYou) 29 | 30 | [AndroidFilePicker](https://github.com/rosuH/AndroidFilePicker) 31 | 32 | 33 | ## Stargazers over time 34 | 35 | [![Stargazers over time](https://starchart.cc/dadaewq/Install-Lion.svg)](https://starchart.cc/dadaewq/Install-Lion) 36 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | buildToolsVersion '30.0.0 rc2' 6 | defaultConfig { 7 | resConfig 'zh' 8 | applicationId "com.modosa.apkinstaller" 9 | minSdkVersion 22 10 | targetSdkVersion 29 11 | versionCode 41 12 | versionName "3.7.4" 13 | flavorDimensions "default" 14 | } 15 | 16 | buildTypes { 17 | release { 18 | shrinkResources true 19 | minifyEnabled true 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | 24 | productFlavors { 25 | normal { 26 | } 27 | miui { 28 | applicationId "com.miui.packageinstaller" 29 | versionNameSuffix "-miui" 30 | } 31 | } 32 | 33 | compileOptions { 34 | sourceCompatibility JavaVersion.VERSION_1_8 35 | targetCompatibility JavaVersion.VERSION_1_8 36 | } 37 | 38 | android.applicationVariants.all { variant -> 39 | variant.outputs.all { 40 | outputFileName = "${applicationId}_${versionName}_${versionCode}.apk" 41 | } 42 | } 43 | } 44 | 45 | dependencies { 46 | implementation fileTree(include: ['*.jar'], dir: 'libs') 47 | def appCenterSdkVersion = '3.1.0' 48 | implementation "com.microsoft.appcenter:appcenter-analytics:${appCenterSdkVersion}" 49 | implementation "com.microsoft.appcenter:appcenter-crashes:${appCenterSdkVersion}" 50 | 51 | implementation 'androidx.appcompat:appcompat:1.1.0' 52 | implementation 'androidx.preference:preference:1.1.1' 53 | implementation 'io.reactivex.rxjava2:rxjava:2.2.13' 54 | implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' 55 | implementation 'com.catchingnow.icebox:SDK:1.0.5' 56 | implementation 'com.github.heruoxin.Delegated-Scopes-Manager:client:master-SNAPSHOT' 57 | implementation 'moe.shizuku.privilege:api:3.1.0' 58 | implementation 'me.rosuh:AndroidFilePicker:0.6.1' 59 | implementation 'com.drakeet.about:about:2.4.1' 60 | implementation 'com.drakeet.multitype:multitype:4.2.0' 61 | implementation 'androidx.browser:browser:1.2.0' 62 | 63 | // implementation "androidx.recyclerview:recyclerview-selection:1.1.0-rc01" 64 | // implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4' 65 | // 66 | // implementation 'com.google.android.material:material:1.2.0-alpha05' 67 | // implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.71" 68 | // implementation 'com.afollestad.material-dialogs:core:3.3.0' 69 | 70 | 71 | // implementation 'com.dyhdyh.loadingbar2:loadingbar:2.0.1' 72 | // 73 | // implementation 'com.github.ForgetAll:LoadingDialog:v1.1.2' 74 | } 75 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | -keep class java.util.concurrent.Flow.* 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | #-keep public class * extends androidx.versionedparcelable.VersionedParcelable { 19 | # (); 20 | 21 | # If you keep the line number information, uncomment this to 22 | # hide the original source file name. 23 | #-renamesourcefileattribute SourceFile 24 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/activity/AboutActivity.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.activity; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.os.Bundle; 5 | import android.view.Menu; 6 | import android.view.MenuItem; 7 | import android.widget.ImageView; 8 | import android.widget.TextView; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.appcompat.app.ActionBar; 12 | 13 | import com.drakeet.about.AbsAboutActivity; 14 | import com.drakeet.about.Card; 15 | import com.drakeet.about.Category; 16 | import com.drakeet.about.Contributor; 17 | import com.drakeet.about.License; 18 | import com.modosa.apkinstaller.BuildConfig; 19 | import com.modosa.apkinstaller.R; 20 | import com.modosa.apkinstaller.util.OpUtil; 21 | 22 | import java.util.List; 23 | 24 | /** 25 | * @author dadaewq 26 | */ 27 | public class AboutActivity extends AbsAboutActivity { 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | 33 | ActionBar actionBar = getSupportActionBar(); 34 | if (actionBar != null) { 35 | actionBar.setDisplayHomeAsUpEnabled(true); 36 | } 37 | } 38 | 39 | @Override 40 | public boolean onCreateOptionsMenu(Menu menu) { 41 | getMenuInflater().inflate(R.menu.menu_about, menu); 42 | return true; 43 | } 44 | 45 | @Override 46 | public boolean onOptionsItemSelected(MenuItem item) { 47 | if (item.getItemId() == android.R.id.home) { 48 | finish(); 49 | return true; 50 | } else if (item.getItemId() == R.id.rate) { 51 | OpUtil.launchCustomTabsUrl(this, "https://www.coolapk.com/apk/com.modosa.apkinstaller"); 52 | return true; 53 | } else { 54 | return super.onOptionsItemSelected(item); 55 | } 56 | } 57 | 58 | @SuppressLint("SetTextI18n") 59 | @Override 60 | protected void onCreateHeader(@NonNull ImageView icon, @NonNull TextView slogan, @NonNull TextView version) { 61 | icon.setImageResource(R.mipmap.ic_launcher); 62 | slogan.setText(getString(R.string.app_name)); 63 | version.setText("v" + BuildConfig.VERSION_NAME + "(" + BuildConfig.VERSION_CODE + ")"); 64 | } 65 | 66 | @Override 67 | protected void onItemsCreated(@NonNull List items) { 68 | items.add(new Category(getString(R.string.title_Introduction))); 69 | items.add(new Card(getString(R.string.desc_Introduction))); 70 | 71 | items.add(new Category(getString(R.string.title_developer))); 72 | items.add(new Contributor(R.drawable.avatar_dadaewq_circle_middle, "dadaewq", getString(R.string.desc_developer), "http://www.coolapk.com/u/460110")); 73 | 74 | items.add(new Category(getString(R.string.title_other_works))); 75 | items.add(new Contributor(R.mipmap.ic_dark_mode_switch, getString(R.string.name_dark_mode_switch), getString(R.string.desc_dark_mode_switch), "https://www.coolapk.com/apk/com.modosa.switchnightui")); 76 | 77 | 78 | items.add(new Category(getString(R.string.title_licenses))); 79 | items.add(new License("Install-Lion", "dadaewq", "", "https://github.com/dadaewq/Install-Lion")); 80 | items.add(new License("MultiType", "drakeet", License.APACHE_2, "https://github.com/drakeet/MultiType")); 81 | items.add(new License("MultiType", "drakeet", License.APACHE_2, "https://github.com/drakeet/MultiType")); 82 | items.add(new License("about-page", "drakeet", License.APACHE_2, "https://github.com/drakeet/about-page")); 83 | items.add(new License("PokeInstaller", "bavelee", "", "https://github.com/bavelee/PokeInstaller")); 84 | items.add(new License("RootInstaller", "Bave", License.GPL_V3, "https://gitee.com/Bave/RootInstaller")); 85 | items.add(new License("IceBox-SDK", "heruoxin", "", "https://github.com/heruoxin/IceBox-SDK")); 86 | items.add(new License("Delegated-Scopes-Manager", "heruoxin", "WTFPL", "https://github.com/heruoxin/Delegated-Scopes-Manager")); 87 | items.add(new License("Shizuku", "Rikka", "", "https://github.com/RikkaApps/Shizuku")); 88 | items.add(new License("FreezeYou", "Playhi", License.APACHE_2, "https://github.com/Playhi/FreezeYou")); 89 | items.add(new License("SAI", "Aefyr", License.GPL_V3, "https://github.com/Aefyr/SAI")); 90 | items.add(new License("AndroidFilePicker", "rosuH", License.MIT, "https://github.com/rosuH/AndroidFilePicker")); 91 | items.add(new License("AndroidX", "Google", License.APACHE_2, "https://source.google.com")); 92 | items.add(new License("RxJava", "ReactiveX", License.APACHE_2, "https://github.com/ReactiveX/RxJava")); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/activity/Install1Activity.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.activity; 2 | 3 | import android.net.Uri; 4 | import android.util.Log; 5 | import android.widget.Toast; 6 | 7 | import androidx.core.content.FileProvider; 8 | 9 | import com.catchingnow.icebox.sdk_client.IceBox; 10 | import com.modosa.apkinstaller.R; 11 | 12 | import java.io.File; 13 | 14 | import io.reactivex.Single; 15 | import io.reactivex.android.schedulers.AndroidSchedulers; 16 | import io.reactivex.disposables.Disposable; 17 | import io.reactivex.schedulers.Schedulers; 18 | 19 | /** 20 | * @author dadaewq 21 | */ 22 | public class Install1Activity extends AbstractInstallerActivity { 23 | public final static String CHANNEL_ID = "1"; 24 | private Disposable mSubscribe; 25 | 26 | @Override 27 | public void startInstall(String getinstallApkPath) { 28 | Log.d("Start install", getinstallApkPath + ""); 29 | if (getinstallApkPath != null) { 30 | installApkFile = new File(getinstallApkPath); 31 | String authority = getPackageName() + ".FILE_PROVIDER"; 32 | Uri installuri = FileProvider.getUriForFile(getApplicationContext(), authority, installApkFile); 33 | showMyToast0(String.format(getString(R.string.tip_start_install), apkinfo[0])); 34 | 35 | disposeSafety(); 36 | 37 | //查询如果为SUPPORTED才开始安装,避免因为没有安装等情况导致installPackage没有返回值 38 | //(疑似部分设备即使安装了查询下也是NOT_INSTALLED) 39 | if (IceBox.SilentInstallSupport.SUPPORTED.equals(IceBox.querySupportSilentInstall(this))) { 40 | mSubscribe = Single.fromCallable(() -> IceBox.installPackage(this, installuri)) 41 | .subscribeOn(Schedulers.io()) 42 | .observeOn(AndroidSchedulers.mainThread()) 43 | .subscribe((Boolean success) -> { 44 | Toast.makeText(this, success ? String.format(getString(R.string.tip_success_install), apkinfo[0]) : String.format(getString(R.string.tip_failed_install), apkinfo[0]), Toast.LENGTH_SHORT).show(); 45 | showNotificationWithdeleteCache(CHANNEL_ID, success); 46 | 47 | }, Throwable::printStackTrace); 48 | } else { 49 | Toast.makeText(this, String.format(getString(R.string.tip_failed_install), apkinfo[0]), Toast.LENGTH_SHORT).show(); 50 | showNotificationWithdeleteCache(CHANNEL_ID, false); 51 | } 52 | finish(); 53 | } else { 54 | showMyToast0(R.string.tip_failed_read); 55 | finish(); 56 | } 57 | } 58 | 59 | @Override 60 | protected void startUninstall(String pkgName) { 61 | Log.d("Start uninstall", pkgName); 62 | showMyToast0(String.format(getString(R.string.tip_start_uninstall), uninstallPackageLable)); 63 | disposeSafety(); 64 | 65 | mSubscribe = Single.fromCallable(() -> IceBox.uninstallPackage(this, pkgName)) 66 | .subscribeOn(Schedulers.io()) 67 | .observeOn(AndroidSchedulers.mainThread()) 68 | .subscribe((Boolean success) -> Toast.makeText(this, success ? String.format(getString(R.string.tip_success_uninstall), uninstallPackageLable) : String.format(getString(R.string.tip_failed_uninstall), uninstallPackageLable), Toast.LENGTH_SHORT).show(), Throwable::printStackTrace); 69 | finish(); 70 | 71 | } 72 | 73 | 74 | private void disposeSafety() { 75 | if (mSubscribe != null && !mSubscribe.isDisposed()) { 76 | mSubscribe.dispose(); 77 | } 78 | mSubscribe = null; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/activity/Install2Activity.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.activity; 2 | 3 | import android.net.Uri; 4 | import android.os.Build; 5 | import android.os.Bundle; 6 | import android.os.Looper; 7 | import android.util.Log; 8 | 9 | import androidx.core.content.FileProvider; 10 | 11 | import com.catchingnow.delegatedscopeclient.DSMClient; 12 | import com.modosa.apkinstaller.R; 13 | import com.modosa.apkinstaller.util.NotifyUtil; 14 | 15 | import java.io.File; 16 | 17 | 18 | /** 19 | * @author dadaewq 20 | */ 21 | public class Install2Activity extends AbstractInstallerActivity { 22 | public final static String CHANNEL_ID = "2"; 23 | private final boolean ltsdk26 = Build.VERSION.SDK_INT < Build.VERSION_CODES.O; 24 | private String installApkPath; 25 | 26 | @Override 27 | public void onCreate(Bundle bundle) { 28 | super.onCreate(bundle); 29 | if (ltsdk26) { 30 | deleteCache(); 31 | } 32 | } 33 | 34 | @Override 35 | public void startInstall(String getinstallApkPath) { 36 | if (ltsdk26) { 37 | showMyToast1(R.string.tip_ltsdk26); 38 | finish(); 39 | } else { 40 | Log.d("Start install", getinstallApkPath + ""); 41 | if (getinstallApkPath != null) { 42 | installApkPath = getinstallApkPath; 43 | installApkFile = new File(installApkPath); 44 | String authority = getPackageName() + ".FILE_PROVIDER"; 45 | Uri installuri = FileProvider.getUriForFile(getApplicationContext(), authority, installApkFile); 46 | new Thread(() -> { 47 | Looper.prepare(); 48 | showMyToast0(String.format(getString(R.string.tip_start_install), apkinfo[0])); 49 | try { 50 | DSMClient.installApp(this, installuri, null); 51 | } catch (Exception e) { 52 | e.printStackTrace(); 53 | } finally { 54 | showNotificationWithdeleteCache(CHANNEL_ID, false); 55 | } 56 | showMyToast1(R.string.tip_try_install_end); 57 | Looper.loop(); 58 | finish(); 59 | }).start(); 60 | } else { 61 | showMyToast0(R.string.tip_failed_read); 62 | finish(); 63 | } 64 | } 65 | } 66 | 67 | @Override 68 | protected void startUninstall(String pkgName) { 69 | if (ltsdk26) { 70 | showMyToast1(R.string.tip_ltsdk26); 71 | finish(); 72 | } else { 73 | Log.d("Start uninstall", pkgName); 74 | new Thread(() -> { 75 | Looper.prepare(); 76 | showMyToast0(String.format(getString(R.string.tip_start_uninstall), uninstallPackageLable)); 77 | try { 78 | DSMClient.uninstallApp(this, pkgName); 79 | } catch (Exception e) { 80 | showMyToast1(e.toString()); 81 | } 82 | Looper.loop(); 83 | //Todo show result 84 | }).start(); 85 | } 86 | } 87 | 88 | @Override 89 | void showNotificationWithdeleteCache(String channelId, boolean success) { 90 | Log.e("packagename", apkinfo[1]); 91 | new NotifyUtil(this).sendNotification(CHANNEL_ID, String.format(getString(R.string.content_title_install_end), apkinfo[0]), apkinfo[1], installApkPath, istemp, false); 92 | } 93 | } -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/activity/Install3Activity.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.activity; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageManager; 5 | import android.os.Build; 6 | import android.os.Looper; 7 | import android.util.Log; 8 | 9 | import androidx.annotation.Nullable; 10 | 11 | import com.modosa.apkinstaller.R; 12 | import com.modosa.apkinstaller.util.ResultUtil; 13 | import com.modosa.apkinstaller.util.apksource.ApkSource; 14 | import com.modosa.apkinstaller.util.installer.ApkSourceBuilder; 15 | import com.modosa.apkinstaller.util.installer.SAIPackageInstaller; 16 | import com.modosa.apkinstaller.util.installer.shizuku.ShizukuSAIPackageInstaller; 17 | import com.modosa.apkinstaller.util.shell.Shell; 18 | import com.modosa.apkinstaller.util.shell.ShizukuShell; 19 | 20 | import java.io.File; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | 25 | /** 26 | * @author dadaewq 27 | */ 28 | public class Install3Activity extends AbstractInstallerActivity implements SAIPackageInstaller.InstallationStatusListener { 29 | public final static String CHANNEL_ID = "3"; 30 | private long mOngoingSessionId; 31 | 32 | @Override 33 | public void startInstall(String getinstallApkPath) { 34 | Log.d("Start install", getinstallApkPath + ""); 35 | if (getinstallApkPath != null) { 36 | installApkFile = new File(getinstallApkPath); 37 | ArrayList files = new ArrayList<>(); 38 | files.add(installApkFile); 39 | new Thread(() -> { 40 | Looper.prepare(); 41 | showMyToast0(String.format(getString(R.string.tip_start_install), apkinfo[0])); 42 | try { 43 | installPackages(files); 44 | } catch (Exception e) { 45 | e.printStackTrace(); 46 | } 47 | finish(); 48 | Looper.loop(); 49 | } 50 | 51 | ).start(); 52 | } 53 | } 54 | 55 | 56 | @Override 57 | protected void startUninstall(String pkgName) { 58 | Log.d("Start uninstall", pkgName); 59 | new Thread(() -> { 60 | Looper.prepare(); 61 | if (!ShizukuShell.getInstance().isAvailable()) { 62 | copyErr(String.format("%s\n\n%s\n%s", getString(R.string.title_dialog_uninstall), alertDialogMessage, getString(R.string.installer_error_shizuku_unavailable))); 63 | showMyToast1(String.format(getString(R.string.tip_failed_uninstall_witherror), uninstallPackageLable, getString(R.string.installer_error_shizuku_unavailable))); 64 | } else { 65 | Shell.Result uninstallationResult = ShizukuShell.getInstance().exec(new Shell.Command("pm", "uninstall", pkgName)); 66 | if (0 == uninstallationResult.exitCode) { 67 | showMyToast0(String.format(getString(R.string.tip_success_uninstall), uninstallPackageLable)); 68 | } else { 69 | String installerVersion = "???"; 70 | try { 71 | installerVersion = getPackageManager().getPackageInfo(getPackageName(), 0).versionName; 72 | } catch (PackageManager.NameNotFoundException ignore) { 73 | } 74 | String info = String.format("%s: %s %s | %s | Android %s | Install Lion %s\n\n", getString(R.string.installer_device), Build.BRAND, Build.MODEL, ResultUtil.isMiui() ? "MIUI" : " ", Build.VERSION.RELEASE, installerVersion); 75 | copyErr(info + uninstallationResult.toString()); 76 | showMyToast1(String.format(getString(R.string.tip_failed_uninstall_witherror), uninstallPackageLable, uninstallationResult.err)); 77 | } 78 | } 79 | Looper.loop(); 80 | } 81 | ).start(); 82 | finish(); 83 | } 84 | 85 | private void installPackages(List apkFiles) { 86 | Context mContext = getApplication(); 87 | SAIPackageInstaller mInstaller = ShizukuSAIPackageInstaller.getInstance(mContext); 88 | mInstaller.addStatusListener(this); 89 | ApkSource apkSource = new ApkSourceBuilder() 90 | .fromApkFiles(apkFiles) 91 | .build(); 92 | mOngoingSessionId = mInstaller.createInstallationSession(apkSource); 93 | mInstaller.startInstallationSession(mOngoingSessionId); 94 | } 95 | 96 | 97 | @Override 98 | public void onStatusChanged(long installationID, SAIPackageInstaller.InstallationStatus status, @Nullable String packageNameOrErrorDescription) { 99 | if (installationID != mOngoingSessionId) { 100 | return; 101 | } 102 | Log.e("status", status + ""); 103 | switch (status) { 104 | case QUEUED: 105 | case INSTALLING: 106 | break; 107 | case INSTALLATION_SUCCEED: 108 | showMyToast0(String.format(getString(R.string.tip_success_install), apkinfo[0])); 109 | showNotificationWithdeleteCache(CHANNEL_ID, true); 110 | break; 111 | case INSTALLATION_FAILED: 112 | 113 | if (packageNameOrErrorDescription != null) { 114 | copyErr(packageNameOrErrorDescription); 115 | showMyToast1(String.format(getString(R.string.tip_failed_install_witherror), apkinfo[0], packageNameOrErrorDescription)); 116 | } else { 117 | copyErr(getString(R.string.unknown)); 118 | showMyToast1(String.format(getString(R.string.tip_failed_install_witherror), apkinfo[0], "")); 119 | } 120 | 121 | showNotificationWithdeleteCache(CHANNEL_ID, false); 122 | break; 123 | default: 124 | finish(); 125 | } 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/activity/Install5Activity.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.activity; 2 | 3 | import android.content.Context; 4 | import android.net.Uri; 5 | import android.os.Build; 6 | import android.os.Looper; 7 | import android.util.Log; 8 | 9 | import com.modosa.apkinstaller.R; 10 | import com.modosa.apkinstaller.util.PackageInstallerUtil; 11 | import com.modosa.apkinstaller.util.ResultUtil; 12 | 13 | import java.io.File; 14 | 15 | /** 16 | * @author dadaewq 17 | */ 18 | public class Install5Activity extends AbstractInstallerActivity { 19 | public final static String CHANNEL_ID = "5"; 20 | private final Context context = this; 21 | private String uninstallPkgName; 22 | 23 | 24 | @Override 25 | public void startInstall(String getinstallApkPath) { 26 | Log.d("Start install", getinstallApkPath + ""); 27 | if (getinstallApkPath != null) { 28 | installApkFile = new File(getinstallApkPath); 29 | new InstallApkTask().start(); 30 | } 31 | } 32 | 33 | @Override 34 | protected void startUninstall(String getUninstallPkgName) { 35 | uninstallPkgName = getUninstallPkgName; 36 | new UninstallApkTask().start(); 37 | } 38 | 39 | 40 | private class InstallApkTask extends Thread { 41 | @Override 42 | public void run() { 43 | Looper.prepare(); 44 | super.run(); 45 | 46 | showMyToast0(String.format(getString(R.string.tip_start_install), apkinfo[0])); 47 | try { 48 | String result = PackageInstallerUtil.installPackage(context, Uri.fromFile(installApkFile), null); 49 | if (result == null) { 50 | showMyToast0(String.format(getString(R.string.tip_success_install), apkinfo[0])); 51 | } else { 52 | String err = String.format("%s: %s %s | %s | Android %s \n", getString(R.string.installer_device), Build.BRAND, Build.MODEL, ResultUtil.isMiui() ? "MIUI" : " ", Build.VERSION.RELEASE) + 53 | String.format(alertDialogMessage + "\n%s", result); 54 | copyErr(err); 55 | showMyToast1(String.format(getString(R.string.tip_failed_install_witherror), apkinfo[0], result)); 56 | } 57 | if (result == null) { 58 | showNotificationWithdeleteCache(CHANNEL_ID, true); 59 | } else { 60 | showNotificationWithdeleteCache(CHANNEL_ID, false); 61 | } 62 | } catch (Exception e) { 63 | e.printStackTrace(); 64 | } 65 | } 66 | } 67 | 68 | private class UninstallApkTask extends Thread { 69 | @Override 70 | public void run() { 71 | Looper.prepare(); 72 | super.run(); 73 | Log.d("Start uninstall", uninstallPkgName); 74 | 75 | try { 76 | String result = PackageInstallerUtil.uninstallPackage(context, uninstallPkgName); 77 | if (result == null) { 78 | showMyToast0(String.format(getString(R.string.tip_success_uninstall), uninstallPackageLable)); 79 | } else { 80 | copyErr(String.format("%s: %s %s | %s | Android %s \n\n%s\n%s", getString(R.string.installer_device), Build.BRAND, Build.MODEL, ResultUtil.isMiui() ? "MIUI" : " ", Build.VERSION.RELEASE, alertDialogMessage, result)); 81 | showMyToast1(String.format(getString(R.string.tip_failed_uninstall_witherror), uninstallPackageLable, result)); 82 | } 83 | } catch (Exception e) { 84 | e.printStackTrace(); 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/activity/Install6Activity.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.activity; 2 | 3 | import android.content.Intent; 4 | import android.net.Uri; 5 | import android.util.Log; 6 | import android.util.Pair; 7 | 8 | import androidx.preference.PreferenceManager; 9 | 10 | import com.modosa.apkinstaller.R; 11 | import com.modosa.apkinstaller.fragment.MainFragment; 12 | import com.modosa.apkinstaller.util.AppInfoUtil; 13 | import com.modosa.apkinstaller.util.OpUtil; 14 | 15 | import java.io.File; 16 | 17 | /** 18 | * @author dadaewq 19 | */ 20 | public class Install6Activity extends AbstractInstallerActivity { 21 | 22 | private boolean needDelete = false; 23 | 24 | 25 | @Override 26 | public void startInstall(String getinstallApkPath) { 27 | startOperate(true, getinstallApkPath); 28 | } 29 | 30 | @Override 31 | protected void startUninstall(String uninstallPkgname) { 32 | startOperate(false, uninstallPkgname); 33 | } 34 | 35 | 36 | private void startOperate(boolean isInstall, String param) { 37 | 38 | if (isInstall) { 39 | installApkFile = new File(param); 40 | needDelete = istemp; 41 | istemp = false; 42 | Log.d("Start install", param + ""); 43 | } else { 44 | Log.d("Start uninstall", param + ""); 45 | } 46 | 47 | boolean prepareOperate; 48 | String getCustomInstaller = PreferenceManager.getDefaultSharedPreferences(this).getString(MainFragment.SP_KEY_CUSTOM_INSTALLER, "").trim(); 49 | if (!"".equals(getCustomInstaller)) { 50 | Intent startIntent; 51 | if (isInstall) { 52 | startIntent = new Intent(Intent.ACTION_VIEW) 53 | .setDataAndType(OpUtil.getMyContentUriForFile(this, installApkFile), "application/vnd.android.package-archive") 54 | .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 55 | .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 56 | } else { 57 | startIntent = new Intent(Intent.ACTION_DELETE, Uri.parse("package:" + param)) 58 | .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 59 | } 60 | 61 | Pair pair = OpUtil.getStartIntentPair(getCustomInstaller, startIntent); 62 | prepareOperate = pair.first; 63 | startIntent = pair.second; 64 | 65 | 66 | if (prepareOperate) { 67 | try { 68 | String installerLable = AppInfoUtil.getCustomInstallerLable(this, getCustomInstaller); 69 | if (isInstall) { 70 | showMyToast0(String.format(getString(R.string.tip_use_to_install), installerLable, apkinfo[0])); 71 | } else { 72 | showMyToast0(String.format(getString(R.string.tip_use_to_uninstall), installerLable, uninstallPackageLable)); 73 | } 74 | Log.e("intent0", ": " + startIntent); 75 | startActivity(startIntent); 76 | needDelete = false; 77 | } catch (Exception e) { 78 | showMyToast1(String.format(getString(R.string.tip_invalid__custom_installer), getCustomInstaller)); 79 | e.printStackTrace(); 80 | } 81 | } else { 82 | showMyToast1(String.format(getString(R.string.tip_invalid__custom_installer), getCustomInstaller)); 83 | } 84 | } else { 85 | showMyToast1(R.string.tip_not_set_custom_installer); 86 | } 87 | 88 | if (isInstall && needDelete) { 89 | OpUtil.deleteSingleFile(installApkFile); 90 | } 91 | finish(); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/activity/LaunchAppActivity.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.activity; 2 | 3 | 4 | import android.app.Activity; 5 | import android.app.NotificationManager; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.os.Bundle; 9 | import android.util.Log; 10 | import android.widget.Toast; 11 | 12 | import com.modosa.apkinstaller.R; 13 | import com.modosa.apkinstaller.util.OpUtil; 14 | 15 | import java.io.File; 16 | 17 | 18 | /** 19 | * @author dadaewq 20 | */ 21 | public class LaunchAppActivity extends Activity { 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | 27 | init(); 28 | finish(); 29 | } 30 | 31 | private void init() { 32 | Intent getIntent = getIntent(); 33 | 34 | if (getIntent != null) { 35 | if (getIntent.hasExtra("notificationId")) { 36 | NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 37 | if (notificationManager != null) { 38 | notificationManager.cancel(getIntent.getExtras().getInt("notificationId")); 39 | } 40 | } 41 | String EXTRA_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME"; 42 | String extraRealPath = "realPath"; 43 | 44 | if (getIntent.hasExtra("isTemp")) { 45 | if (getIntent.hasExtra(extraRealPath)) { 46 | File apkFile = new File(getIntent.getStringExtra(extraRealPath) + ""); 47 | OpUtil.startAnotherInstaller(this, apkFile, getIntent.getBooleanExtra("isTemp", false)); 48 | 49 | } 50 | } else if (getIntent.hasExtra(EXTRA_PACKAGE_NAME)) { 51 | String packagename = getIntent.getStringExtra(EXTRA_PACKAGE_NAME) + ""; 52 | Log.d("PACKAGE_NAME ==>", packagename); 53 | try { 54 | Intent intent = getPackageManager().getLaunchIntentForPackage(packagename); 55 | startActivity(intent); 56 | } catch (Exception e) { 57 | Toast.makeText(this, e + "", Toast.LENGTH_LONG).show(); 58 | } 59 | } else { 60 | if (getIntent.hasExtra(extraRealPath)) { 61 | File apkFile = new File(getIntent.getStringExtra(extraRealPath) + ""); 62 | OpUtil.deleteSingleFile(apkFile); 63 | 64 | if (apkFile.exists()) { 65 | Toast.makeText(this, R.string.tip_delete_apk_fail, Toast.LENGTH_SHORT).show(); 66 | } else { 67 | Toast.makeText(this, R.string.tip_delete_apk_success, Toast.LENGTH_SHORT).show(); 68 | } 69 | 70 | } 71 | } 72 | } 73 | 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/activity/ManageAllowSourceActivity.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.activity; 2 | 3 | import android.app.AlertDialog; 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | import android.os.Bundle; 7 | import android.view.Menu; 8 | import android.view.MenuItem; 9 | import android.view.View; 10 | import android.widget.CheckBox; 11 | import android.widget.ListView; 12 | import android.widget.SimpleAdapter; 13 | 14 | import androidx.appcompat.app.ActionBar; 15 | import androidx.appcompat.app.AppCompatActivity; 16 | import androidx.appcompat.widget.Toolbar; 17 | 18 | import com.modosa.apkinstaller.R; 19 | import com.modosa.apkinstaller.util.AppInfoUtil; 20 | import com.modosa.apkinstaller.util.OpUtil; 21 | 22 | import java.util.ArrayList; 23 | import java.util.HashMap; 24 | import java.util.List; 25 | 26 | 27 | public class ManageAllowSourceActivity extends AppCompatActivity { 28 | 29 | public static final String SP_KEY_ALLOWSOURCE = "allowsource"; 30 | private SharedPreferences spAllowSource; 31 | private ListView uaamListView; 32 | private AlertDialog alertDialog; 33 | 34 | 35 | @Override 36 | protected void onCreate(Bundle savedInstanceState) { 37 | super.onCreate(savedInstanceState); 38 | setContentView(R.layout.manage_allowsource); 39 | Toolbar toolbar = findViewById(R.id.toolbar); 40 | setSupportActionBar(toolbar); 41 | 42 | ActionBar actionBar = getSupportActionBar(); 43 | if (actionBar != null) { 44 | actionBar.setDisplayHomeAsUpEnabled(true); 45 | } 46 | 47 | spAllowSource = getSharedPreferences(SP_KEY_ALLOWSOURCE, Context.MODE_PRIVATE); 48 | uaamListView = findViewById(R.id.uaam_listView); 49 | refreshList(); 50 | } 51 | 52 | @Override 53 | public boolean onCreateOptionsMenu(Menu menu) { 54 | getMenuInflater().inflate(R.menu.menu_clear_allowource, menu); 55 | return true; 56 | } 57 | 58 | @Override 59 | public void onResume() { 60 | super.onResume(); 61 | if (alertDialog != null) { 62 | alertDialog.dismiss(); 63 | } 64 | refreshList(); 65 | } 66 | 67 | @Override 68 | public boolean onOptionsItemSelected(MenuItem item) { 69 | if (item.getItemId() == android.R.id.home) { 70 | finish(); 71 | return true; 72 | } else if (item.getItemId() == R.id.clearAllowedList) { 73 | showDialogClearAllowedList(); 74 | } 75 | return super.onOptionsItemSelected(item); 76 | } 77 | 78 | @Override 79 | public void onDestroy() { 80 | super.onDestroy(); 81 | if (alertDialog != null) { 82 | alertDialog.dismiss(); 83 | } 84 | } 85 | 86 | private void refreshList() { 87 | 88 | final String allowsourceString = spAllowSource.getString(SP_KEY_ALLOWSOURCE, ""); 89 | 90 | if ("".equals(allowsourceString)) { 91 | List pkgList = new ArrayList<>(); 92 | StringHashMap hashMap = new StringHashMap(); 93 | hashMap.put("Lable", getString(R.string.empty)); 94 | hashMap.put("PkgName", ""); 95 | pkgList.add(hashMap); 96 | final SimpleAdapter adapter = new SimpleAdapter(this, pkgList, 97 | R.layout.manage_allowsource_view, new String[]{"Lable", "PkgName"}, new int[]{R.id.uaaml_name, R.id.uaaml_pkgName}); 98 | uaamListView.setAdapter(adapter); 99 | uaamListView.setOnItemClickListener(null); 100 | } else { 101 | List pkgList = new ArrayList<>(); 102 | final String[] strings = allowsourceString.split(","); 103 | 104 | for (String aString : strings) { 105 | 106 | if (!"".equals(aString)) { 107 | StringHashMap hashMap = new StringHashMap(); 108 | hashMap.put("Lable", 109 | AppInfoUtil.getApplicationLabel( 110 | this, aString)); 111 | hashMap.put("PkgName", aString); 112 | pkgList.add(hashMap); 113 | } 114 | } 115 | final SimpleAdapter adapter = new SimpleAdapter(this, pkgList, 116 | R.layout.manage_allowsource_view, new String[]{"Lable", "PkgName"}, new int[]{R.id.uaaml_name, R.id.uaaml_pkgName}); 117 | uaamListView.setAdapter(adapter); 118 | Context context = this; 119 | uaamListView.setOnItemClickListener((parent, view, position, id) -> { 120 | StringHashMap hashMap = (StringHashMap) adapter.getItem(position); 121 | final String pkgName = hashMap.get("PkgName"); 122 | if (pkgName != null) { 123 | 124 | AlertDialog.Builder builder = new AlertDialog.Builder(context) 125 | .setTitle(R.string.message_confirm_remove_list) 126 | .setMessage(hashMap.get("Lable") + System.getProperty("line.separator") + pkgName) 127 | .setNeutralButton(android.R.string.cancel, null) 128 | .setPositiveButton(android.R.string.ok, (dialog, which) -> { 129 | List ls = OpUtil.convertToList(strings); 130 | ls.remove(pkgName); 131 | spAllowSource.edit().putString(SP_KEY_ALLOWSOURCE, OpUtil.listToString(ls, ",")).apply(); 132 | refreshList(); 133 | }); 134 | 135 | alertDialog = builder.create(); 136 | OpUtil.showAlertDialog(context, alertDialog); 137 | 138 | } 139 | }); 140 | } 141 | } 142 | 143 | private void showDialogClearAllowedList() { 144 | 145 | View checkBoxView = View.inflate(this, R.layout.confirm_checkbox, null); 146 | CheckBox checkBox = checkBoxView.findViewById(R.id.confirm_checkbox); 147 | 148 | checkBox.setText(R.string.checkbox_clearAllowedList); 149 | checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(isChecked)); 150 | 151 | AlertDialog.Builder builder = new AlertDialog.Builder(this) 152 | .setTitle(R.string.title_clearAllowedList) 153 | .setMessage(R.string.message_clearAllowedList) 154 | .setView(checkBoxView) 155 | .setNeutralButton(android.R.string.cancel, null) 156 | .setPositiveButton(android.R.string.ok, (dialog, which) -> { 157 | getSharedPreferences(SP_KEY_ALLOWSOURCE, Context.MODE_PRIVATE).edit().clear().apply(); 158 | refreshList(); 159 | }); 160 | 161 | 162 | alertDialog = builder.create(); 163 | OpUtil.showAlertDialog(this, alertDialog); 164 | 165 | alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); 166 | } 167 | 168 | private static class StringHashMap extends HashMap { 169 | } 170 | } 171 | 172 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/activity/SettingsActivity.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.activity; 2 | 3 | 4 | import android.os.Bundle; 5 | import android.view.MenuItem; 6 | import android.widget.Toast; 7 | 8 | import androidx.appcompat.app.ActionBar; 9 | import androidx.appcompat.app.AppCompatActivity; 10 | import androidx.appcompat.widget.Toolbar; 11 | import androidx.fragment.app.FragmentManager; 12 | 13 | import com.modosa.apkinstaller.R; 14 | import com.modosa.apkinstaller.fragment.SettingsFragment; 15 | 16 | 17 | /** 18 | * @author dadaewq 19 | */ 20 | public class SettingsActivity extends AppCompatActivity { 21 | 22 | private static final String TAG_SETTINGS = "settings"; 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | 28 | setContentView(R.layout.activity_main_ui); 29 | Toolbar toolbar = findViewById(R.id.toolbar); 30 | setSupportActionBar(toolbar); 31 | ActionBar actionBar = getSupportActionBar(); 32 | if (actionBar != null) { 33 | actionBar.setDisplayHomeAsUpEnabled(true); 34 | } 35 | 36 | FragmentManager fragmentManager = getSupportFragmentManager(); 37 | 38 | fragmentManager.beginTransaction().replace(R.id.framelayout, new SettingsFragment(), TAG_SETTINGS).commit(); 39 | 40 | } 41 | 42 | private void showMyToast0(final String text) { 43 | runOnUiThread(() -> Toast.makeText(this, text, Toast.LENGTH_SHORT).show()); 44 | } 45 | 46 | private void showMyToast0(final int stringId) { 47 | runOnUiThread(() -> Toast.makeText(this, stringId, Toast.LENGTH_SHORT).show()); 48 | } 49 | 50 | @Override 51 | public boolean onOptionsItemSelected(MenuItem item) { 52 | if (item.getItemId() == android.R.id.home) { 53 | finish(); 54 | return true; 55 | } else { 56 | return super.onOptionsItemSelected(item); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/activity/StartMainUiActivity.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.activity; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | 6 | import com.modosa.apkinstaller.util.OpUtil; 7 | 8 | public class StartMainUiActivity extends Activity { 9 | 10 | @Override 11 | protected void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | OpUtil.startMainUiActivity(this); 14 | finish(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/base/Application.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.base; 2 | 3 | import android.content.SharedPreferences; 4 | 5 | import androidx.appcompat.app.AppCompatDelegate; 6 | import androidx.preference.PreferenceManager; 7 | 8 | import com.microsoft.appcenter.AppCenter; 9 | import com.microsoft.appcenter.analytics.Analytics; 10 | import com.microsoft.appcenter.crashes.Crashes; 11 | import com.modosa.apkinstaller.fragment.SettingsFragment; 12 | 13 | @SuppressWarnings("WeakerAccess") 14 | public class Application extends android.app.Application { 15 | 16 | 17 | @Override 18 | public void onCreate() { 19 | super.onCreate(); 20 | SharedPreferences spGetPreferenceManager = PreferenceManager.getDefaultSharedPreferences(this); 21 | if (!spGetPreferenceManager.getBoolean(SettingsFragment.SP_KEY_DISABLE_BUG_REPORT, false)) { 22 | String scretCode = null; 23 | //TODO 24 | 25 | 26 | AppCenter.start(this, scretCode, Analytics.class, Crashes.class); 27 | } 28 | int nightMode = spGetPreferenceManager.getInt(SettingsFragment.SP_KEY_NIGHT_MODE, AppCompatDelegate.MODE_NIGHT_UNSPECIFIED); 29 | AppCompatDelegate.setDefaultNightMode(nightMode); 30 | 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/receiver/AdminReceiver.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.receiver; 2 | 3 | import android.app.admin.DeviceAdminReceiver; 4 | import android.content.ComponentName; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | 8 | import androidx.annotation.NonNull; 9 | 10 | import com.modosa.apkinstaller.R; 11 | import com.modosa.apkinstaller.util.OpUtil; 12 | 13 | /** 14 | * @author dadaewq 15 | */ 16 | public class AdminReceiver extends DeviceAdminReceiver { 17 | 18 | public static ComponentName getComponentName(Context context) { 19 | return new ComponentName(context.getApplicationContext(), AdminReceiver.class); 20 | } 21 | 22 | @Override 23 | public void onEnabled(@NonNull Context context, @NonNull Intent intent) { 24 | super.onEnabled(context, intent); 25 | OpUtil.showToast1(context, R.string.tip_start_activating); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/receiver/SecretCodeReceiver.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.receiver; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.provider.Telephony; 7 | import android.telephony.TelephonyManager; 8 | 9 | import com.modosa.apkinstaller.util.OpUtil; 10 | 11 | /** 12 | * @author dadaewq 13 | */ 14 | public class SecretCodeReceiver extends BroadcastReceiver { 15 | 16 | @Override 17 | public void onReceive(final Context context, Intent intent) { 18 | 19 | String action = intent.getAction(); 20 | if (Telephony.Sms.Intents.SECRET_CODE_ACTION.equals(action) || TelephonyManager.ACTION_SECRET_CODE.equals(action)) { 21 | OpUtil.startMainUiActivity(context); 22 | } 23 | 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/util/AppInfoUtil.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.util; 2 | 3 | import android.content.ComponentName; 4 | import android.content.Context; 5 | import android.content.pm.ActivityInfo; 6 | import android.content.pm.ApplicationInfo; 7 | import android.content.pm.PackageInfo; 8 | import android.content.pm.PackageManager; 9 | import android.graphics.Bitmap; 10 | import android.graphics.Canvas; 11 | import android.graphics.PixelFormat; 12 | import android.graphics.drawable.BitmapDrawable; 13 | import android.graphics.drawable.Drawable; 14 | import android.os.Build; 15 | 16 | import com.modosa.apkinstaller.R; 17 | 18 | import static android.content.pm.PackageManager.GET_UNINSTALLED_PACKAGES; 19 | import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; 20 | 21 | /** 22 | * @author dadaewq 23 | */ 24 | public final class AppInfoUtil { 25 | 26 | public static String getApplicationLabel(Context context, String pkgName) { 27 | PackageManager pm = context.getPackageManager(); 28 | ApplicationInfo applicationInfo = null; 29 | try { 30 | applicationInfo = pm.getApplicationInfo(pkgName, 0); 31 | } catch (Exception ignore) { 32 | } 33 | 34 | if (applicationInfo != null) { 35 | return applicationInfo.loadLabel(pm).toString(); 36 | } else { 37 | try { 38 | return (pm.getApplicationInfo(pkgName, Build.VERSION.SDK_INT > Build.VERSION_CODES.M ? MATCH_UNINSTALLED_PACKAGES : GET_UNINSTALLED_PACKAGES).loadLabel(pm).toString()); 39 | } catch (Exception e) { 40 | e.printStackTrace(); 41 | return context.getString(R.string.unknown); 42 | } 43 | } 44 | } 45 | 46 | private static String getActivityLabel(Context context, ComponentName componentName) { 47 | PackageManager pm = context.getPackageManager(); 48 | 49 | ActivityInfo activityInfo = null; 50 | try { 51 | activityInfo = pm.getActivityInfo(componentName, 0); 52 | } catch (Exception ignore) { 53 | } 54 | 55 | if (activityInfo != null) { 56 | return activityInfo.loadLabel(pm).toString(); 57 | } else { 58 | try { 59 | return (pm.getActivityInfo(componentName, Build.VERSION.SDK_INT > Build.VERSION_CODES.M ? MATCH_UNINSTALLED_PACKAGES : GET_UNINSTALLED_PACKAGES).loadLabel(pm).toString()); 60 | } catch (Exception e) { 61 | e.printStackTrace(); 62 | return context.getString(R.string.unknown); 63 | } 64 | } 65 | } 66 | 67 | public static String getCustomInstallerLable(Context context, String getCustomInstaller) { 68 | String installerLable = context.getString(R.string.unknown); 69 | if (getCustomInstaller.contains("/")) { 70 | String[] names = getCustomInstaller.split("/"); 71 | if (names.length == 2 && !"".equals(names[0]) && !"".equals(names[1])) { 72 | if (names[1].length() > 1 && names[1].startsWith(".")) { 73 | names[1] = names[0] + names[1]; 74 | } 75 | installerLable = getActivityLabel(context, new ComponentName(names[0], names[1])); 76 | } 77 | 78 | } else { 79 | installerLable = getApplicationLabel(context, getCustomInstaller); 80 | } 81 | return installerLable; 82 | } 83 | 84 | public static String[] getApplicationVersion(Context context, String pkgName) { 85 | PackageManager pm = context.getPackageManager(); 86 | ApplicationInfo applicationInfo = null; 87 | try { 88 | applicationInfo = pm.getApplicationInfo(pkgName, 0); 89 | } catch (Exception ignore) { 90 | } 91 | 92 | if (applicationInfo != null) { 93 | return getApkVersion(context, applicationInfo.sourceDir); 94 | } 95 | return null; 96 | 97 | 98 | } 99 | 100 | 101 | public static Drawable getApplicationIconDrawable(Context context, String pkgName) { 102 | PackageManager pm = context.getPackageManager(); 103 | ApplicationInfo applicationInfo = null; 104 | try { 105 | applicationInfo = pm.getApplicationInfo(pkgName, 0); 106 | } catch (Exception ignore) { 107 | } 108 | 109 | if (applicationInfo != null) { 110 | return applicationInfo.loadIcon(pm); 111 | } else { 112 | try { 113 | return pm.getApplicationInfo(pkgName, Build.VERSION.SDK_INT > Build.VERSION_CODES.M ? MATCH_UNINSTALLED_PACKAGES : GET_UNINSTALLED_PACKAGES).loadIcon(pm); 114 | } catch (Exception ignore) { 115 | return pm.getDefaultActivityIcon(); 116 | } 117 | } 118 | } 119 | 120 | static Bitmap getApplicationIconBitmap(Context context, String pkgName) { 121 | return drawable2Bitmap(getApplicationIconDrawable(context, pkgName)); 122 | } 123 | 124 | public static String[] getApkInfo(Context context, String apkPath) { 125 | PackageManager pm = context.getPackageManager(); 126 | PackageInfo pkgInfo = pm.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES); 127 | 128 | if (pkgInfo != null) { 129 | pkgInfo.applicationInfo.sourceDir = apkPath; 130 | pkgInfo.applicationInfo.publicSourceDir = apkPath; 131 | 132 | return new String[]{ 133 | pm.getApplicationLabel(pkgInfo.applicationInfo).toString(), 134 | pkgInfo.packageName, 135 | pkgInfo.versionName, 136 | Build.VERSION.SDK_INT < Build.VERSION_CODES.P ? Integer.toString(pkgInfo.versionCode) : Long.toString(pkgInfo.getLongVersionCode()), FileSizeUtil.getAutoFolderOrFileSize(apkPath) 137 | }; 138 | } else { 139 | return null; 140 | } 141 | } 142 | 143 | 144 | static String[] getApkVersion(Context context, String apkPath) { 145 | PackageManager pm = context.getPackageManager(); 146 | PackageInfo pkgInfo = pm.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES); 147 | 148 | if (pkgInfo != null) { 149 | pkgInfo.applicationInfo.sourceDir = apkPath; 150 | pkgInfo.applicationInfo.publicSourceDir = apkPath; 151 | 152 | 153 | return new String[]{ 154 | pkgInfo.versionName, 155 | Build.VERSION.SDK_INT < Build.VERSION_CODES.P ? Integer.toString(pkgInfo.versionCode) : Long.toString(pkgInfo.getLongVersionCode()), FileSizeUtil.getAutoFolderOrFileSize(apkPath) 156 | }; 157 | } else { 158 | return null; 159 | } 160 | } 161 | 162 | public static Drawable getApkIconDrawable(Context context, String apkPath) { 163 | Drawable icon = null; 164 | PackageManager pm = context.getPackageManager(); 165 | PackageInfo pkgInfo = pm.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES); 166 | if (pkgInfo != null) { 167 | ApplicationInfo applicationInfo = pkgInfo.applicationInfo; 168 | pkgInfo.applicationInfo.sourceDir = apkPath; 169 | pkgInfo.applicationInfo.publicSourceDir = apkPath; 170 | icon = applicationInfo.loadIcon(pm); 171 | } 172 | if (icon == null) { 173 | icon = pm.getDefaultActivityIcon(); 174 | } 175 | return icon; 176 | } 177 | 178 | static Bitmap getApkIconBitmap(Context context, String apkPath) { 179 | return drawable2Bitmap(getApkIconDrawable(context, apkPath)); 180 | } 181 | 182 | private static Bitmap drawable2Bitmap(Drawable drawable) { 183 | 184 | Bitmap bitmap = null; 185 | 186 | if (drawable instanceof BitmapDrawable) { 187 | BitmapDrawable bd = (BitmapDrawable) drawable; 188 | bitmap = bd.getBitmap(); 189 | } 190 | 191 | if (bitmap == null && drawable != null) { 192 | bitmap = Bitmap 193 | .createBitmap( 194 | drawable.getIntrinsicWidth(), 195 | drawable.getIntrinsicHeight(), 196 | drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 197 | : Bitmap.Config.RGB_565); 198 | Canvas canvas = new Canvas(bitmap); 199 | drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), 200 | drawable.getIntrinsicHeight()); 201 | drawable.draw(canvas); 202 | } 203 | 204 | return bitmap; 205 | } 206 | 207 | 208 | } 209 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/util/FileSizeUtil.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.util; 2 | 3 | 4 | import android.util.Log; 5 | 6 | import java.io.File; 7 | import java.text.DecimalFormat; 8 | 9 | /** 10 | * @author dadaewq 11 | * android Get the size of a folder or file In units of B, KB, MB, GB 12 | * https://github.com/haiyuKing/FileSizeUtilDemo/blob/master/app/src/main/java/com/why/project/filesizeutildemo/utils/FileSizeUtil.java 13 | */ 14 | public class FileSizeUtil { 15 | 16 | private static final int SIZETYPE_B = 1; 17 | private static final int SIZETYPE_KB = 2; 18 | private static final int SIZETYPE_MB = 3; 19 | private static final int SIZETYPE_GB = 4; 20 | 21 | /** 22 | * 获取指定文件或指定文件夹的的指定单位的大小 23 | * 24 | * @param filePath 文件路径 25 | * @param sizeType 获取大小的类型1为B、2为KB、3为MB、4为GB 26 | * @return double值的大小 27 | */ 28 | static double getFolderOrFileSize(String filePath, int sizeType) { 29 | File file = new File(filePath); 30 | long blockSize = 0; 31 | try { 32 | if (file.isDirectory()) { 33 | blockSize = getFolderSize(file); 34 | } else { 35 | blockSize = getFileSize(file); 36 | } 37 | } catch (Exception e) { 38 | e.printStackTrace(); 39 | Log.e("getFolderOrFileSize", "Fail !"); 40 | } 41 | return formetFileSize(blockSize, sizeType); 42 | } 43 | 44 | /** 45 | * 调用此方法自动计算指定文件或指定文件夹的大小 46 | * 47 | * @param filePath 文件路径 48 | * @return 计算好的带B、KB、MB、GB的字符串 49 | */ 50 | public static String getAutoFolderOrFileSize(String filePath) { 51 | File file = new File(filePath); 52 | long blockSize = 0; 53 | try { 54 | if (file.isDirectory()) { 55 | blockSize = getFolderSize(file); 56 | } else { 57 | blockSize = getFileSize(file); 58 | } 59 | } catch (Exception e) { 60 | e.printStackTrace(); 61 | Log.e("getAutoFolderOrFileSize", "Fail !"); 62 | } 63 | return formetFileSize(blockSize); 64 | } 65 | 66 | /** 67 | * 获取指定文件的大小 68 | * 69 | * @param file 指定文件 70 | * @return longValue 71 | */ 72 | private static long getFileSize(File file) { 73 | return file.length(); 74 | } 75 | 76 | /** 77 | * 获取指定文件夹的大小 78 | * 79 | * @param file 指定文件夹 80 | * @return longValue 81 | * @throws Exception FolderNotFound 82 | */ 83 | private static long getFolderSize(File file) throws Exception { 84 | long size = 0; 85 | File[] flist = file.listFiles(); 86 | if (flist == null) { 87 | Log.e("getFolderSize", "notExist!"); 88 | throw new Exception("FolderNotFound"); 89 | } else { 90 | for (File value : flist) { 91 | if (value.isDirectory()) { 92 | size = size + getFolderSize(value); 93 | } else { 94 | size = size + getFileSize(value); 95 | } 96 | } 97 | return size; 98 | } 99 | 100 | } 101 | 102 | /** 103 | * 转换文件大小 104 | * 105 | * @param fileSize 文件大小 106 | * @return 格式化显示 107 | */ 108 | private static String formetFileSize(long fileSize) { 109 | DecimalFormat df = new DecimalFormat("#.00"); 110 | String fileSizeString; 111 | String wrongSize = "0B"; 112 | if (fileSize == 0) { 113 | return wrongSize; 114 | } 115 | if (fileSize < 1024) { 116 | fileSizeString = df.format((double) fileSize) + "B"; 117 | } else if (fileSize < 1048576) { 118 | fileSizeString = df.format((double) fileSize / 1024) + "KB"; 119 | } else if (fileSize < 1073741824) { 120 | fileSizeString = df.format((double) fileSize / 1048576) + "MB"; 121 | } else { 122 | fileSizeString = df.format((double) fileSize / 1073741824) + "GB"; 123 | } 124 | return fileSizeString; 125 | } 126 | 127 | /** 128 | * 转换文件大小,指定转换的类型 129 | * 130 | * @param fileSize 文件大小 131 | * @param sizeType 指定转换的类型 132 | * @return 格式化显示 133 | */ 134 | private static double formetFileSize(long fileSize, int sizeType) { 135 | DecimalFormat df = new DecimalFormat("#.00"); 136 | double fileSizeLong = 0; 137 | switch (sizeType) { 138 | case SIZETYPE_B: 139 | fileSizeLong = Double.parseDouble(df.format((double) fileSize)); 140 | break; 141 | case SIZETYPE_KB: 142 | fileSizeLong = Double.parseDouble(df.format((double) fileSize / 1024)); 143 | break; 144 | case SIZETYPE_MB: 145 | fileSizeLong = Double.parseDouble(df.format((double) fileSize / 1048576)); 146 | break; 147 | case SIZETYPE_GB: 148 | fileSizeLong = Double.parseDouble(df.format((double) fileSize / 1073741824)); 149 | break; 150 | default: 151 | } 152 | return fileSizeLong; 153 | } 154 | } -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/util/IOUtils.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.util; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.InputStreamReader; 9 | import java.io.OutputStream; 10 | 11 | public class IOUtils { 12 | private static final String TAG = "IOUtils"; 13 | 14 | public static void copyStream(InputStream from, OutputStream to) throws IOException { 15 | byte[] buf = new byte[1024 * 1024]; 16 | int len; 17 | while ((len = from.read(buf)) > 0) { 18 | to.write(buf, 0, len); 19 | } 20 | } 21 | 22 | public static Thread writeStreamToStringBuilder(StringBuilder builder, InputStream inputStream) { 23 | Thread t = new Thread(() -> { 24 | try { 25 | char[] buf = new char[1024]; 26 | int len; 27 | BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); 28 | while ((len = reader.read(buf)) > 0) { 29 | builder.append(buf, 0, len); 30 | } 31 | 32 | reader.close(); 33 | } catch (Exception e) { 34 | Log.wtf(TAG, e); 35 | } 36 | }); 37 | t.start(); 38 | return t; 39 | } 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/util/PackageInstallerUtil.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.util; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.PendingIntent; 5 | import android.content.BroadcastReceiver; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.IntentFilter; 9 | import android.content.IntentSender; 10 | import android.content.pm.PackageInstaller; 11 | import android.net.Uri; 12 | import android.text.TextUtils; 13 | 14 | import androidx.annotation.Nullable; 15 | 16 | import com.modosa.apkinstaller.R; 17 | 18 | import java.io.IOException; 19 | import java.io.InputStream; 20 | import java.io.OutputStream; 21 | import java.util.Objects; 22 | import java.util.concurrent.atomic.AtomicBoolean; 23 | 24 | 25 | /** 26 | * @author heruoxin @ CatchingNow Inc. 27 | * @since 2018/11/6 28 | */ 29 | public class PackageInstallerUtil { 30 | 31 | public static String installPackage(Context context, Uri uri, @Nullable String packageName) throws IOException { 32 | try (InputStream in = context.getContentResolver().openInputStream(uri)) { 33 | 34 | final AtomicBoolean o = new AtomicBoolean(); 35 | final StringBuilder stringBuilder = new StringBuilder(); 36 | final String name = context.getPackageName() + "_install_" + System.currentTimeMillis(); 37 | Context app = context.getApplicationContext(); 38 | app.registerReceiver(new BroadcastReceiver() { 39 | @Override 40 | public void onReceive(Context context, Intent intent) { 41 | app.unregisterReceiver(this); 42 | int statusCode = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE); 43 | if (statusCode != PackageInstaller.STATUS_SUCCESS) { 44 | String message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE); 45 | 46 | if (TextUtils.isEmpty(message)) { 47 | stringBuilder.append(context.getString(R.string.unknown)); 48 | } else { 49 | stringBuilder.append(message); 50 | } 51 | 52 | } 53 | 54 | o.set(PackageInstaller.STATUS_SUCCESS == statusCode); 55 | synchronized (o) { 56 | o.notify(); 57 | } 58 | } 59 | }, new IntentFilter(name)); 60 | 61 | PackageInstaller packageInstaller = app.getPackageManager().getPackageInstaller(); 62 | PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( 63 | PackageInstaller.SessionParams.MODE_FULL_INSTALL); 64 | if (!TextUtils.isEmpty(packageName)) { 65 | params.setAppPackageName(packageName); 66 | } 67 | // set params 68 | int sessionId = packageInstaller.createSession(params); 69 | PackageInstaller.Session session = packageInstaller.openSession(sessionId); 70 | OutputStream out = session.openWrite(name, 0, -1); 71 | byte[] buffer = new byte[65536]; 72 | int c; 73 | while ((c = Objects.requireNonNull(in).read(buffer)) != -1) { 74 | out.write(buffer, 0, c); 75 | } 76 | session.fsync(out); 77 | out.close(); 78 | session.commit(createIntentSender(app, sessionId, name)); 79 | 80 | synchronized (o) { 81 | try { 82 | o.wait(); 83 | if (o.get()) { 84 | return null; 85 | } else { 86 | return stringBuilder.toString(); 87 | } 88 | } catch (InterruptedException e) { 89 | return stringBuilder.append("\n").toString(); 90 | } 91 | } 92 | } 93 | } 94 | 95 | @SuppressLint("MissingPermission") 96 | public static String uninstallPackage(Context context, String packageName) { 97 | final AtomicBoolean o = new AtomicBoolean(); 98 | final StringBuilder stringBuilder = new StringBuilder(); 99 | final String name = context.getPackageName() + "_uninstall_" + System.currentTimeMillis(); 100 | Context app = context.getApplicationContext(); 101 | app.registerReceiver(new BroadcastReceiver() { 102 | @Override 103 | public void onReceive(Context context, Intent intent) { 104 | app.unregisterReceiver(this); 105 | int statusCode = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE); 106 | if (statusCode != PackageInstaller.STATUS_SUCCESS) { 107 | String message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE); 108 | 109 | if (TextUtils.isEmpty(message)) { 110 | stringBuilder.append(context.getString(R.string.unknown)); 111 | } else { 112 | stringBuilder.append(message); 113 | } 114 | } 115 | o.set(PackageInstaller.STATUS_SUCCESS == statusCode); 116 | synchronized (o) { 117 | o.notify(); 118 | } 119 | } 120 | }, new IntentFilter(name)); 121 | 122 | PackageInstaller mPackageInstaller = app.getPackageManager().getPackageInstaller(); 123 | try { 124 | mPackageInstaller.uninstall(packageName, createIntentSender(app, name.hashCode(), name)); 125 | } catch (Exception e) { 126 | return e.toString(); 127 | } 128 | 129 | 130 | synchronized (o) { 131 | try { 132 | o.wait(); 133 | if (o.get()) { 134 | return null; 135 | } else { 136 | return stringBuilder.toString(); 137 | } 138 | } catch (InterruptedException e) { 139 | return stringBuilder.append("\n").toString(); 140 | } 141 | } 142 | } 143 | 144 | 145 | private static IntentSender createIntentSender(Context context, int sessionId, String name) { 146 | PendingIntent pendingIntent = PendingIntent.getBroadcast( 147 | context, 148 | sessionId, 149 | new Intent(name), 150 | 0); 151 | return pendingIntent.getIntentSender(); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/util/ResultUtil.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.util; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.text.TextUtils; 5 | import android.util.Log; 6 | 7 | import androidx.annotation.Nullable; 8 | 9 | import java.io.PrintWriter; 10 | import java.io.StringWriter; 11 | 12 | public class ResultUtil { 13 | private static final String TAG = "ResultUtil"; 14 | 15 | public static String throwableToString(Throwable throwable) { 16 | StringWriter sw = new StringWriter(1024); 17 | PrintWriter pw = new PrintWriter(sw); 18 | 19 | throwable.printStackTrace(pw); 20 | pw.close(); 21 | 22 | return sw.toString(); 23 | } 24 | 25 | @SuppressLint("PrivateApi") 26 | @Nullable 27 | private static String getSystemProperty() { 28 | try { 29 | return (String) Class.forName("android.os.SystemProperties") 30 | .getDeclaredMethod("get", String.class) 31 | .invoke(null, "ro.miui.ui.version.name"); 32 | } catch (Exception e) { 33 | Log.w("SAIUtils", "Unable to use SystemProperties.get", e); 34 | return null; 35 | } 36 | } 37 | 38 | 39 | public static boolean isMiui() { 40 | return !TextUtils.isEmpty(getSystemProperty()); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/util/ShellUtil.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.util; 2 | // Copyright (C) 2018 Bave Lee 3 | // This file is part of Quick-Android. 4 | // https://github.com/Crixec/Quick-Android 5 | // 6 | // Quick-Android is free software: you can redistribute it and/or modify 7 | // it under the terms of the GNU General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // Quick-Android is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU General Public License 17 | 18 | import android.util.Log; 19 | 20 | import java.io.BufferedReader; 21 | import java.io.Closeable; 22 | import java.io.DataOutputStream; 23 | import java.io.IOException; 24 | import java.io.InputStreamReader; 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | 28 | public class ShellUtil { 29 | 30 | private static String[] exec(final String sh, final List cmds) { 31 | final String[] myResult = new String[4]; 32 | Process process; 33 | DataOutputStream stdin = null; 34 | OutputReader stdout = null; 35 | OutputReader stderr = null; 36 | try { 37 | process = Runtime.getRuntime().exec(sh); 38 | stdin = new DataOutputStream(process.getOutputStream()); 39 | 40 | ArrayList errlist = new ArrayList<>(); 41 | StringBuilder stringBuilder = new StringBuilder(); 42 | stdout = new OutputReader(new BufferedReader(new InputStreamReader(process.getInputStream())), 43 | text -> { 44 | myResult[0] = text; 45 | // Log.e("onStdout", myResult[0]); 46 | }); 47 | 48 | stderr = new OutputReader(new BufferedReader(new InputStreamReader(process.getErrorStream())), 49 | errlist::add); 50 | 51 | stdout.start(); 52 | stderr.start(); 53 | for (String cmd : cmds) { 54 | myResult[2] = cmd; 55 | // Log.e("cmd", myResult[2]); 56 | stdin.write(cmd.getBytes()); 57 | stdin.writeBytes("\n"); 58 | stdin.flush(); 59 | } 60 | stdin.writeBytes("exit $?\n"); 61 | stdin.flush(); 62 | int resultCode = process.waitFor(); 63 | 64 | myResult[3] = resultCode + ""; 65 | // Log.e("resultCode", myResult[3] + ""); 66 | 67 | if (errlist.size() == 1) { 68 | myResult[1] = errlist.get(0); 69 | } else { 70 | int num = errlist.size() < 6 ? errlist.size() : 5; 71 | 72 | for (int i = 1; i < num; i++) { 73 | stringBuilder.append(errlist.get(i)).append("\n"); 74 | } 75 | myResult[1] = stringBuilder + ""; 76 | } 77 | 78 | Log.e("onStderr===>", myResult[1]); 79 | 80 | } catch (Exception e) { 81 | e.printStackTrace(); 82 | } finally { 83 | 84 | safeCancel(stderr); 85 | safeCancel(stdout); 86 | safeClose(stdout); 87 | safeClose(stderr); 88 | safeClose(stdin); 89 | 90 | } 91 | return myResult; 92 | } 93 | 94 | private static void safeCancel(OutputReader reader) { 95 | try { 96 | if (reader != null) { 97 | reader.cancel(); 98 | } 99 | } catch (Exception ignored) { 100 | 101 | } 102 | } 103 | 104 | private static void safeClose(Closeable closeable) { 105 | try { 106 | if (closeable != null) { 107 | closeable.close(); 108 | } 109 | } catch (Exception ignored) { 110 | 111 | } 112 | } 113 | 114 | private static String[] exec(final List cmds, final boolean isRoot) { 115 | String sh = isRoot ? "su" : "sh"; 116 | return exec(sh, cmds); 117 | } 118 | 119 | 120 | private static String[] exec(final String cmd, boolean isRoot) { 121 | List cmds = new ArrayList<>(); 122 | cmds.add(cmd); 123 | return exec(cmds, isRoot); 124 | } 125 | 126 | 127 | public static String[] execWithRoot(final String cmd) { 128 | return exec(cmd, true); 129 | } 130 | 131 | private interface Output { 132 | void output(String text); 133 | } 134 | 135 | static class OutputReader extends Thread implements Closeable { 136 | private final Output output; 137 | private final BufferedReader reader; 138 | private boolean isRunning; 139 | 140 | private OutputReader(BufferedReader reader, Output output) { 141 | this.output = output; 142 | this.reader = reader; 143 | this.isRunning = true; 144 | } 145 | 146 | @Override 147 | public void close() { 148 | try { 149 | reader.close(); 150 | } catch (IOException ignored) { 151 | } 152 | } 153 | 154 | @Override 155 | public void run() { 156 | super.run(); 157 | String line; 158 | while (isRunning) { 159 | try { 160 | line = reader.readLine(); 161 | if (line != null) { 162 | output.output(line); 163 | } 164 | } catch (IOException ignored) { 165 | } 166 | } 167 | } 168 | 169 | private void cancel() { 170 | synchronized (this) { 171 | isRunning = false; 172 | this.notifyAll(); 173 | } 174 | } 175 | } 176 | } -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/util/apksource/ApkSource.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.util.apksource; 2 | 3 | import java.io.InputStream; 4 | 5 | public interface ApkSource extends AutoCloseable { 6 | 7 | boolean nextApk(); 8 | 9 | InputStream openApkInputStream() throws Exception; 10 | 11 | long getApkLength(); 12 | 13 | @Override 14 | default void close() { 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/util/apksource/DefaultApkSource.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.util.apksource; 2 | 3 | import com.modosa.apkinstaller.util.filedescriptor.FileDescriptor; 4 | 5 | import java.io.InputStream; 6 | import java.util.List; 7 | 8 | public class DefaultApkSource implements ApkSource { 9 | 10 | private final List mApkFileDescriptors; 11 | private FileDescriptor mCurrentApk; 12 | 13 | public DefaultApkSource(List apkFileDescriptors) { 14 | mApkFileDescriptors = apkFileDescriptors; 15 | } 16 | 17 | @Override 18 | public boolean nextApk() { 19 | if (mApkFileDescriptors.size() == 0) { 20 | return false; 21 | } 22 | 23 | mCurrentApk = mApkFileDescriptors.remove(0); 24 | return true; 25 | } 26 | 27 | @Override 28 | public InputStream openApkInputStream() throws Exception { 29 | return mCurrentApk.open(); 30 | } 31 | 32 | @Override 33 | public long getApkLength() { 34 | return mCurrentApk.length(); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/util/filedescriptor/FileDescriptor.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.util.filedescriptor; 2 | 3 | import java.io.InputStream; 4 | 5 | public interface FileDescriptor { 6 | 7 | long length(); 8 | 9 | InputStream open() throws Exception; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/util/filedescriptor/NormalFileDescriptor.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.util.filedescriptor; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.InputStream; 6 | 7 | public class NormalFileDescriptor implements FileDescriptor { 8 | 9 | private final File mFile; 10 | 11 | public NormalFileDescriptor(File file) { 12 | mFile = file; 13 | } 14 | 15 | 16 | @Override 17 | public long length() { 18 | return mFile.length(); 19 | } 20 | 21 | @Override 22 | public InputStream open() throws Exception { 23 | return new FileInputStream(mFile); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/util/installer/ApkSourceBuilder.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.util.installer; 2 | 3 | import com.modosa.apkinstaller.util.apksource.ApkSource; 4 | import com.modosa.apkinstaller.util.apksource.DefaultApkSource; 5 | import com.modosa.apkinstaller.util.filedescriptor.FileDescriptor; 6 | import com.modosa.apkinstaller.util.filedescriptor.NormalFileDescriptor; 7 | 8 | import java.io.File; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public class ApkSourceBuilder { 13 | 14 | 15 | private boolean mSourceSet; 16 | private List mApkFiles; 17 | 18 | 19 | public ApkSourceBuilder fromApkFiles(List apkFiles) { 20 | ensureSourceSetOnce(); 21 | mApkFiles = apkFiles; 22 | return this; 23 | } 24 | 25 | public ApkSource build() { 26 | ApkSource apkSource; 27 | 28 | if (mApkFiles != null) { 29 | List apkFileDescriptors = new ArrayList<>(mApkFiles.size()); 30 | for (File apkFile : mApkFiles) { 31 | apkFileDescriptors.add(new NormalFileDescriptor(apkFile)); 32 | } 33 | 34 | apkSource = new DefaultApkSource(apkFileDescriptors); 35 | } else { 36 | throw new IllegalStateException("No source set"); 37 | } 38 | 39 | return apkSource; 40 | } 41 | 42 | private void ensureSourceSetOnce() { 43 | if (mSourceSet) { 44 | throw new IllegalStateException("Source can be only be set once"); 45 | } 46 | mSourceSet = true; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/util/installer/QueuedInstallation.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.util.installer; 2 | 3 | import com.modosa.apkinstaller.util.apksource.ApkSource; 4 | 5 | class QueuedInstallation { 6 | 7 | private final ApkSource mApkSource; 8 | private final long mId; 9 | 10 | QueuedInstallation(ApkSource apkSource, long id) { 11 | mApkSource = apkSource; 12 | mId = id; 13 | } 14 | 15 | public long getId() { 16 | return mId; 17 | } 18 | 19 | ApkSource getApkSource() { 20 | return mApkSource; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/util/installer/SAIPackageInstaller.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.util.installer; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | import android.os.Looper; 6 | import android.util.Log; 7 | import android.util.LongSparseArray; 8 | 9 | import androidx.annotation.Nullable; 10 | 11 | import com.modosa.apkinstaller.util.apksource.ApkSource; 12 | 13 | import java.util.ArrayDeque; 14 | import java.util.ArrayList; 15 | import java.util.concurrent.ExecutorService; 16 | import java.util.concurrent.Executors; 17 | 18 | public abstract class SAIPackageInstaller { 19 | private static final String TAG = "SAIPI"; 20 | private final Context mContext; 21 | private final Handler mHandler = new Handler(Looper.getMainLooper()); 22 | private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); 23 | private final ArrayDeque mInstallationQueue = new ArrayDeque<>(); 24 | private final ArrayList mListeners = new ArrayList<>(); 25 | private final LongSparseArray mCreatedInstallationSessions = new LongSparseArray<>(); 26 | private boolean mInstallationInProgress; 27 | private long mLastInstallationID = 0; 28 | private QueuedInstallation mOngoingInstallation; 29 | 30 | SAIPackageInstaller(Context c) { 31 | mContext = c.getApplicationContext(); 32 | } 33 | 34 | protected Context getContext() { 35 | return mContext; 36 | } 37 | 38 | public void addStatusListener(InstallationStatusListener listener) { 39 | mListeners.add(listener); 40 | } 41 | 42 | public long createInstallationSession(ApkSource apkSource) { 43 | long installationID = mLastInstallationID++; 44 | mCreatedInstallationSessions.put(installationID, new QueuedInstallation(apkSource, installationID)); 45 | return installationID; 46 | } 47 | 48 | public void startInstallationSession(long sessionID) { 49 | QueuedInstallation installation = mCreatedInstallationSessions.get(sessionID); 50 | mCreatedInstallationSessions.remove(sessionID); 51 | if (installation == null) { 52 | return; 53 | } 54 | 55 | mInstallationQueue.addLast(installation); 56 | dispatchSessionUpdate(installation.getId(), InstallationStatus.QUEUED, null); 57 | processQueue(); 58 | } 59 | 60 | private void processQueue() { 61 | if (mInstallationQueue.size() == 0 || mInstallationInProgress) { 62 | return; 63 | } 64 | 65 | QueuedInstallation installation = mInstallationQueue.removeFirst(); 66 | mOngoingInstallation = installation; 67 | mInstallationInProgress = true; 68 | 69 | dispatchCurrentSessionUpdate(InstallationStatus.INSTALLING, null); 70 | 71 | mExecutor.execute(() -> installApkFiles(installation.getApkSource())); 72 | } 73 | 74 | protected abstract void installApkFiles(ApkSource apkSource); 75 | 76 | void installationCompleted() { 77 | Log.d(TAG, String.format("%s->installationCompleted(); mOngoingInstallation.id=%d", getClass().getSimpleName(), dbgGetOngoingInstallationId())); 78 | mInstallationInProgress = false; 79 | mOngoingInstallation = null; 80 | processQueue(); 81 | } 82 | 83 | private void dispatchSessionUpdate(long sessionID, InstallationStatus status, String packageNameOrError) { 84 | mHandler.post(() -> { 85 | Log.d(TAG, String.format("%s->dispatchSessionUpdate(%d, %s, %s)", getClass().getSimpleName(), sessionID, status.name(), packageNameOrError)); 86 | for (InstallationStatusListener listener : mListeners) { 87 | listener.onStatusChanged(sessionID, status, packageNameOrError); 88 | } 89 | }); 90 | } 91 | 92 | void dispatchCurrentSessionUpdate(InstallationStatus status, String packageNameOrError) { 93 | Log.d(TAG, String.format("%s->dispatchCurrentSessionUpdate(%s, %s); mOngoingInstallation.id=%d", getClass().getSimpleName(), status.name(), packageNameOrError, dbgGetOngoingInstallationId())); 94 | dispatchSessionUpdate(mOngoingInstallation.getId(), status, packageNameOrError); 95 | } 96 | 97 | private long dbgGetOngoingInstallationId() { 98 | return mOngoingInstallation != null ? mOngoingInstallation.getId() : -1; 99 | } 100 | 101 | public enum InstallationStatus { 102 | QUEUED, INSTALLING, INSTALLATION_SUCCEED, INSTALLATION_FAILED 103 | } 104 | 105 | public interface InstallationStatusListener { 106 | void onStatusChanged(long installationID, InstallationStatus status, @Nullable String packageNameOrErrorDescription); 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/util/installer/ShellSAIPackageInstaller.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.util.installer; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.BroadcastReceiver; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.IntentFilter; 8 | import android.content.pm.PackageManager; 9 | import android.os.Build; 10 | import android.util.Log; 11 | import android.util.Pair; 12 | 13 | import com.modosa.apkinstaller.BuildConfig; 14 | import com.modosa.apkinstaller.R; 15 | import com.modosa.apkinstaller.util.ResultUtil; 16 | import com.modosa.apkinstaller.util.apksource.ApkSource; 17 | import com.modosa.apkinstaller.util.shell.Shell; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import java.util.concurrent.atomic.AtomicBoolean; 22 | import java.util.regex.Matcher; 23 | import java.util.regex.Pattern; 24 | 25 | /** 26 | * Base class for installers that install packages via pm shell command, child classes must provide a Shell{@link com.modosa.apkinstaller.util.shell} 27 | * Please note, that it's unsafe to use multiple ShellPackageInstaller instances at the same time because installation completion is determined by the ACTION_PACKAGE_ADDED broadcast 28 | */ 29 | public abstract class ShellSAIPackageInstaller extends SAIPackageInstaller { 30 | private static final String TAG = "ShellSAIPI"; 31 | private static final Pattern sessionIdPattern = Pattern.compile("(\\d+)"); 32 | private final AtomicBoolean mIsAwaitingBroadcast = new AtomicBoolean(false); 33 | 34 | protected ShellSAIPackageInstaller(Context c) { 35 | super(c); 36 | IntentFilter packageAddedFilter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 37 | packageAddedFilter.addDataScheme("package"); 38 | BroadcastReceiver mPackageInstalledBroadcastReceiver = new BroadcastReceiver() { 39 | @Override 40 | public void onReceive(Context context, Intent intent) { 41 | Log.d(TAG, intent.toString()); 42 | if (!mIsAwaitingBroadcast.get()) { 43 | return; 44 | } 45 | 46 | String installedPackage = "null"; 47 | try { 48 | installedPackage = intent.getDataString().replace("package:", ""); 49 | String installerPackage = getContext().getPackageManager().getInstallerPackageName(installedPackage); 50 | Log.d(TAG, "installerPackage=" + installerPackage); 51 | if (!installerPackage.equals(BuildConfig.APPLICATION_ID)) { 52 | return; 53 | } 54 | } catch (Exception e) { 55 | Log.wtf(TAG, e); 56 | } 57 | 58 | mIsAwaitingBroadcast.set(false); 59 | dispatchCurrentSessionUpdate(InstallationStatus.INSTALLATION_SUCCEED, installedPackage); 60 | installationCompleted(); 61 | } 62 | }; 63 | getContext().registerReceiver(mPackageInstalledBroadcastReceiver, packageAddedFilter); 64 | } 65 | 66 | @SuppressLint("DefaultLocale") 67 | @Override 68 | protected void installApkFiles(ApkSource aApkSource) { 69 | try (ApkSource apkSource = aApkSource) { 70 | 71 | if (!getShell().isAvailable()) { 72 | dispatchCurrentSessionUpdate(InstallationStatus.INSTALLATION_FAILED, getContext().getString(R.string.installer_error_shell, getInstallerName(), getShellUnavailableMessage())); 73 | installationCompleted(); 74 | return; 75 | } 76 | 77 | int sessionId = createSession(); 78 | 79 | int currentApkFile = 0; 80 | while (apkSource.nextApk()) { 81 | if (apkSource.getApkLength() == -1) { 82 | dispatchCurrentSessionUpdate(InstallationStatus.INSTALLATION_FAILED, getContext().getString(R.string.installer_error_unknown_apk_size)); 83 | installationCompleted(); 84 | return; 85 | } 86 | ensureCommandSucceeded(getShell().exec( 87 | new Shell.Command("pm", "install-write", "-S", 88 | String.valueOf(apkSource.getApkLength()), 89 | String.valueOf(sessionId), String.format("%d.apk", currentApkFile++)), 90 | apkSource.openApkInputStream()) 91 | ); 92 | } 93 | 94 | mIsAwaitingBroadcast.set(true); 95 | Shell.Result installationResult = getShell().exec(new Shell.Command("pm", "install-commit", String.valueOf(sessionId))); 96 | if (!installationResult.isSuccessful()) { 97 | mIsAwaitingBroadcast.set(false); 98 | dispatchCurrentSessionUpdate(InstallationStatus.INSTALLATION_FAILED, getContext().getString(R.string.installer_error_shell, getInstallerName(), getSessionInfo(apkSource) + "\n\n" + installationResult.toString())); 99 | installationCompleted(); 100 | } 101 | } catch (Exception e) { 102 | //TODO this catches resources close exception causing a crash, same in rootless installer 103 | Log.w(TAG, e); 104 | dispatchCurrentSessionUpdate(InstallationStatus.INSTALLATION_FAILED, getContext().getString(R.string.installer_error_shell, getInstallerName(), getSessionInfo(aApkSource) + "\n\n" + ResultUtil.throwableToString(e))); 105 | installationCompleted(); 106 | } 107 | } 108 | 109 | private String ensureCommandSucceeded(Shell.Result result) { 110 | if (!result.isSuccessful()) { 111 | throw new RuntimeException(result.toString()); 112 | } 113 | return result.out; 114 | } 115 | 116 | private String getSessionInfo(ApkSource apkSource) { 117 | String installerVersion = "???"; 118 | try { 119 | installerVersion = getContext().getPackageManager().getPackageInfo(getContext().getPackageName(), 0).versionName; 120 | } catch (PackageManager.NameNotFoundException e) { 121 | Log.wtf(TAG, "Unable to get SAI version", e); 122 | } 123 | return String.format("%s: %s %s | %s | Android %s | Install Lion %s", getContext().getString(R.string.installer_device), Build.BRAND, Build.MODEL, ResultUtil.isMiui() ? "MIUI" : " ", Build.VERSION.RELEASE, installerVersion); 124 | } 125 | 126 | private int createSession() throws RuntimeException { 127 | ArrayList commandsToAttempt = new ArrayList<>(); 128 | commandsToAttempt.add(new Shell.Command("pm", "install-create", "-r", "-d", "--user 0", "--install-location", "0", "-i", getShell().makeLiteral(BuildConfig.APPLICATION_ID))); 129 | commandsToAttempt.add(new Shell.Command("pm", "install-create", "-r", "-d", "-- user 0", "-i", getShell().makeLiteral(BuildConfig.APPLICATION_ID))); 130 | 131 | List> attemptedCommands = new ArrayList<>(); 132 | 133 | for (Shell.Command commandToAttempt : commandsToAttempt) { 134 | Shell.Result result = getShell().exec(commandToAttempt); 135 | attemptedCommands.add(new Pair<>(commandToAttempt, result.toString())); 136 | 137 | if (!result.isSuccessful()) { 138 | Log.w(TAG, String.format("Command failed: %s > %s", commandToAttempt, result)); 139 | continue; 140 | } 141 | 142 | Integer sessionId = extractSessionId(result.out); 143 | if (sessionId != null) { 144 | return sessionId; 145 | } else { 146 | Log.w(TAG, String.format("Command failed: %s > %s", commandToAttempt, result)); 147 | } 148 | } 149 | 150 | StringBuilder exceptionMessage = new StringBuilder("Unable to create session, attempted commands: "); 151 | int i = 1; 152 | for (Pair attemptedCommand : attemptedCommands) { 153 | exceptionMessage.append("\n\n").append(i++).append(") ==========================\n") 154 | .append(attemptedCommand.first) 155 | .append("\nVVVVVVVVVVVVVVVV\n") 156 | .append(attemptedCommand.second); 157 | } 158 | exceptionMessage.append("\n"); 159 | 160 | throw new IllegalStateException(exceptionMessage.toString()); 161 | } 162 | 163 | private Integer extractSessionId(String commandResult) { 164 | try { 165 | 166 | Matcher sessionIdMatcher = sessionIdPattern.matcher(commandResult); 167 | sessionIdMatcher.find(); 168 | return Integer.parseInt(sessionIdMatcher.group(1)); 169 | } catch (Exception e) { 170 | Log.w(TAG, commandResult, e); 171 | return null; 172 | } 173 | } 174 | 175 | protected abstract Shell getShell(); 176 | 177 | protected abstract String getInstallerName(); 178 | 179 | protected abstract String getShellUnavailableMessage(); 180 | } 181 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/util/installer/rooted/RootedSAIPackageInstaller.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.util.installer.rooted; 2 | 3 | import android.content.Context; 4 | 5 | import com.modosa.apkinstaller.R; 6 | import com.modosa.apkinstaller.util.installer.ShellSAIPackageInstaller; 7 | import com.modosa.apkinstaller.util.shell.Shell; 8 | import com.modosa.apkinstaller.util.shell.SuShell; 9 | 10 | public class RootedSAIPackageInstaller extends ShellSAIPackageInstaller { 11 | private static RootedSAIPackageInstaller sInstance; 12 | 13 | private RootedSAIPackageInstaller(Context c) { 14 | super(c); 15 | sInstance = this; 16 | } 17 | 18 | public static RootedSAIPackageInstaller getInstance(Context c) { 19 | synchronized (RootedSAIPackageInstaller.class) { 20 | return sInstance != null ? sInstance : new RootedSAIPackageInstaller(c); 21 | } 22 | } 23 | 24 | @Override 25 | protected Shell getShell() { 26 | return SuShell.getInstance(); 27 | } 28 | 29 | @Override 30 | protected String getInstallerName() { 31 | return "Rooted"; 32 | } 33 | 34 | @Override 35 | protected String getShellUnavailableMessage() { 36 | return getContext().getString(R.string.installer_error_root_no_root); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/util/installer/shizuku/ShizukuSAIPackageInstaller.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.util.installer.shizuku; 2 | 3 | import android.content.Context; 4 | 5 | import com.modosa.apkinstaller.R; 6 | import com.modosa.apkinstaller.util.installer.ShellSAIPackageInstaller; 7 | import com.modosa.apkinstaller.util.shell.Shell; 8 | import com.modosa.apkinstaller.util.shell.ShizukuShell; 9 | 10 | public class ShizukuSAIPackageInstaller extends ShellSAIPackageInstaller { 11 | private static ShizukuSAIPackageInstaller sInstance; 12 | 13 | private ShizukuSAIPackageInstaller(Context c) { 14 | super(c); 15 | sInstance = this; 16 | } 17 | 18 | public static ShizukuSAIPackageInstaller getInstance(Context c) { 19 | synchronized (ShizukuSAIPackageInstaller.class) { 20 | return sInstance != null ? sInstance : new ShizukuSAIPackageInstaller(c); 21 | } 22 | } 23 | 24 | @Override 25 | public Shell getShell() { 26 | return ShizukuShell.getInstance(); 27 | } 28 | 29 | @Override 30 | public String getInstallerName() { 31 | return "Shizuku"; 32 | } 33 | 34 | @Override 35 | public String getShellUnavailableMessage() { 36 | return getContext().getString(R.string.installer_error_shizuku_unavailable); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/util/shell/Shell.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.util.shell; 2 | 3 | import android.annotation.SuppressLint; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import java.io.InputStream; 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | 11 | public interface Shell { 12 | 13 | boolean isAvailable(); 14 | 15 | Result exec(Command command); 16 | 17 | Result exec(Command command, InputStream inputPipe); 18 | 19 | String makeLiteral(String arg); 20 | 21 | class Command { 22 | private final ArrayList mArgs = new ArrayList<>(); 23 | 24 | public Command(String command, String... args) { 25 | mArgs.add(command); 26 | mArgs.addAll(Arrays.asList(args)); 27 | } 28 | 29 | String[] toStringArray() { 30 | String[] array = new String[mArgs.size()]; 31 | 32 | for (int i = 0; i < mArgs.size(); i++) { 33 | array[i] = mArgs.get(i); 34 | } 35 | 36 | return array; 37 | } 38 | 39 | @NonNull 40 | @Override 41 | public String toString() { 42 | StringBuilder sb = new StringBuilder(); 43 | 44 | for (int i = 0; i < mArgs.size(); i++) { 45 | String arg = mArgs.get(i); 46 | sb.append(arg); 47 | if (i < mArgs.size() - 1) { 48 | sb.append(" "); 49 | } 50 | } 51 | 52 | return sb.toString(); 53 | } 54 | 55 | public static class Builder { 56 | private final Command mCommand; 57 | 58 | Builder(String command, String... args) { 59 | mCommand = new Command(command, args); 60 | } 61 | 62 | public Builder addArg(String argument) { 63 | mCommand.mArgs.add(argument); 64 | return this; 65 | } 66 | 67 | Command build() { 68 | return mCommand; 69 | } 70 | } 71 | } 72 | 73 | class Result { 74 | public final int exitCode; 75 | public final String out; 76 | public final String err; 77 | final Command cmd; 78 | 79 | Result(Command cmd, int exitCode, String out, String err) { 80 | this.cmd = cmd; 81 | this.exitCode = exitCode; 82 | this.out = out; 83 | this.err = err; 84 | } 85 | 86 | public boolean isSuccessful() { 87 | return exitCode == 0; 88 | } 89 | 90 | @SuppressLint("DefaultLocale") 91 | @NonNull 92 | @Override 93 | public String toString() { 94 | return String.format("Command: %s\nExit code: %d\nOut:\n%s\n=============\nErr:\n%s", cmd, exitCode, out, err); 95 | } 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/util/shell/ShizukuShell.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.util.shell; 2 | 3 | import android.os.Build; 4 | import android.util.Log; 5 | 6 | import androidx.annotation.Nullable; 7 | 8 | import com.modosa.apkinstaller.util.IOUtils; 9 | import com.modosa.apkinstaller.util.ResultUtil; 10 | 11 | import java.io.InputStream; 12 | import java.io.OutputStream; 13 | 14 | import moe.shizuku.api.RemoteProcess; 15 | import moe.shizuku.api.ShizukuService; 16 | 17 | public class ShizukuShell implements Shell { 18 | private static final String TAG = "ShizukuShell"; 19 | 20 | private static ShizukuShell sInstance; 21 | 22 | private ShizukuShell() { 23 | sInstance = this; 24 | } 25 | 26 | public static ShizukuShell getInstance() { 27 | synchronized (ShizukuShell.class) { 28 | return sInstance != null ? sInstance : new ShizukuShell(); 29 | } 30 | } 31 | 32 | @Override 33 | public boolean isAvailable() { 34 | if (!ShizukuService.pingBinder()) { 35 | return false; 36 | } 37 | 38 | try { 39 | return exec(new Command("echo", "test")).isSuccessful(); 40 | } catch (Exception e) { 41 | Log.w(TAG, "Unable to access shizuku: "); 42 | Log.w(TAG, e); 43 | return false; 44 | } 45 | } 46 | 47 | @Override 48 | public Result exec(Command command) { 49 | return execInternal(command, null); 50 | } 51 | 52 | @Override 53 | public Result exec(Command command, InputStream inputPipe) { 54 | return execInternal(command, inputPipe); 55 | } 56 | 57 | @Override 58 | public String makeLiteral(String arg) { 59 | return "'" + arg.replace("'", "'\\''") + "'"; 60 | } 61 | 62 | private Result execInternal(Command command, @Nullable InputStream inputPipe) { 63 | 64 | StringBuilder stdOutSb = new StringBuilder(); 65 | StringBuilder stdErrSb = new StringBuilder(); 66 | 67 | try { 68 | RemoteProcess process = ShizukuService.newProcess(new String[]{"sh"}, null, null); 69 | Thread stdOutD = IOUtils.writeStreamToStringBuilder(stdOutSb, process.getInputStream()); 70 | Thread stdErrD = IOUtils.writeStreamToStringBuilder(stdErrSb, process.getErrorStream()); 71 | OutputStream outputStream = process.getOutputStream(); 72 | outputStream.write(command.toString().getBytes()); 73 | outputStream.flush(); 74 | 75 | if (inputPipe != null && process.alive()) { 76 | try (InputStream inputStream = inputPipe) { 77 | IOUtils.copyStream(inputStream, outputStream); 78 | } catch (Exception e) { 79 | throw new RuntimeException(e); 80 | } 81 | } 82 | 83 | outputStream.close(); 84 | 85 | process.waitFor(); 86 | stdOutD.join(); 87 | stdErrD.join(); 88 | int exitValue = process.exitValue(); 89 | 90 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 91 | process.destroyForcibly(); 92 | } else { 93 | process.destroy(); 94 | } 95 | 96 | return new Result(command, exitValue, stdOutSb.toString().trim(), stdErrSb.toString().trim()); 97 | } catch (Exception e) { 98 | Log.w(TAG, "Unable execute command: "); 99 | Log.w(TAG, e); 100 | return new Result(command, -1, "", "\n\n SAI ShizukuShell Java exception: " + ResultUtil.throwableToString(e)); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /app/src/main/java/com/modosa/apkinstaller/util/shell/SuShell.java: -------------------------------------------------------------------------------- 1 | package com.modosa.apkinstaller.util.shell; 2 | 3 | import android.os.Build; 4 | import android.util.Log; 5 | 6 | import androidx.annotation.Nullable; 7 | 8 | import com.modosa.apkinstaller.util.IOUtils; 9 | import com.modosa.apkinstaller.util.ResultUtil; 10 | 11 | import java.io.InputStream; 12 | import java.io.OutputStream; 13 | 14 | public class SuShell implements Shell { 15 | private static final String TAG = "SuShell"; 16 | 17 | private static SuShell sInstance; 18 | 19 | private SuShell() { 20 | sInstance = this; 21 | } 22 | 23 | public static SuShell getInstance() { 24 | synchronized (SuShell.class) { 25 | return sInstance != null ? sInstance : new SuShell(); 26 | } 27 | } 28 | 29 | private boolean requestRoot() { 30 | try { 31 | return exec(new Command("exit")).isSuccessful(); 32 | } catch (Exception e) { 33 | Log.w(TAG, "Unable to acquire root access: "); 34 | Log.w(TAG, e); 35 | return false; 36 | } 37 | } 38 | 39 | @Override 40 | public boolean isAvailable() { 41 | return requestRoot(); 42 | } 43 | 44 | @Override 45 | public Result exec(Command command) { 46 | return execInternal(command, null); 47 | } 48 | 49 | @Override 50 | public Result exec(Command command, InputStream inputPipe) { 51 | return execInternal(command, inputPipe); 52 | } 53 | 54 | @Override 55 | public String makeLiteral(String arg) { 56 | return "'" + arg.replace("'", "'\\''") + "'"; 57 | } 58 | 59 | private Result execInternal(Command command, @Nullable InputStream inputPipe) { 60 | StringBuilder stdOutSb = new StringBuilder(); 61 | StringBuilder stdErrSb = new StringBuilder(); 62 | 63 | try { 64 | Command.Builder suCommand = new Command.Builder("su", "-c", command.toString()); 65 | 66 | Process process = Runtime.getRuntime().exec(suCommand.build().toStringArray()); 67 | 68 | 69 | Thread stdOutD = IOUtils.writeStreamToStringBuilder(stdOutSb, process.getInputStream()); 70 | Thread stdErrD = IOUtils.writeStreamToStringBuilder(stdErrSb, process.getErrorStream()); 71 | 72 | if (inputPipe != null) { 73 | try (OutputStream outputStream = process.getOutputStream(); InputStream inputStream = inputPipe) { 74 | IOUtils.copyStream(inputStream, outputStream); 75 | } catch (Exception e) { 76 | stdOutD.interrupt(); 77 | stdErrD.interrupt(); 78 | 79 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 80 | process.destroyForcibly(); 81 | } else { 82 | process.destroy(); 83 | } 84 | 85 | throw new RuntimeException(e); 86 | } 87 | } 88 | 89 | process.waitFor(); 90 | stdOutD.join(); 91 | stdErrD.join(); 92 | 93 | return new Result(command, process.exitValue(), stdOutSb.toString().trim(), stdErrSb.toString().trim()); 94 | } catch (Exception e) { 95 | Log.w(TAG, "Unable execute command: "); 96 | Log.w(TAG, e); 97 | return new Result(command, -1, stdOutSb.toString().trim(), stdErrSb.toString() + "\n\n SAI SuShell Java exception: " + ResultUtil.throwableToString(e)); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/alertdialog_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/alertdialog_background_transparent.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/avatar_dadaewq_circle_middle.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dpm_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_filter_vintage_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_pick.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_rate.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_root_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_transparent.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/my_btn_selector_file_picker_rail.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_text_press_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main_ui.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/app_theme.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 17 | 18 | 24 | 25 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/layout/confirm_checkbox.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/confirmprompt_doublecheckbox.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | 13 | 19 | 20 | 28 | 29 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/hideicon_checkbox.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 15 | 16 | 22 | 23 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/install_by_another.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 25 | 26 | 29 | 30 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/layout/install_content_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 21 | 22 | 29 | 30 | 31 | 32 | 37 | 38 | 45 | 46 | 54 | 55 | 56 | 57 | 64 | 65 | 72 | 73 | 81 | 82 | 83 | 84 | 90 | 91 | 98 | 99 | 107 | 108 | 109 | 110 | 117 | -------------------------------------------------------------------------------- /app/src/main/res/layout/manage_allowsource.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/manage_allowsource_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 20 | 21 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/layout/uninstall_content_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 21 | 22 | 30 | 31 | 32 | 37 | 38 | 45 | 46 | 54 | 55 | 56 | 57 | 66 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_about.xml: -------------------------------------------------------------------------------- 1 | 5 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_clear_allowource.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main_ui.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 12 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_dpm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_dpm_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_root.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_root_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_dpm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-hdpi/ic_dpm.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_dpm_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-hdpi/ic_dpm_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_dpm_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-hdpi/ic_dpm_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_root.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-hdpi/ic_root.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_root_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-hdpi/ic_root_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_root_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-hdpi/ic_root_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_dpm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-mdpi/ic_dpm.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_dpm_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-mdpi/ic_dpm_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_dpm_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-mdpi/ic_dpm_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_root.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-mdpi/ic_root.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_root_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-mdpi/ic_root_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_root_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-mdpi/ic_root_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_dpm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xhdpi/ic_dpm.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_dpm_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xhdpi/ic_dpm_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_dpm_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xhdpi/ic_dpm_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_root.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xhdpi/ic_root.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_root_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xhdpi/ic_root_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_root_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xhdpi/ic_root_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_dpm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xxhdpi/ic_dpm.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_dpm_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xxhdpi/ic_dpm_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_dpm_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xxhdpi/ic_dpm_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_root.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xxhdpi/ic_root.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_root_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xxhdpi/ic_root_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_root_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xxhdpi/ic_root_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_dark_mode_switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xxxhdpi/ic_dark_mode_switch.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_dpm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xxxhdpi/ic_dpm.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_dpm_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xxxhdpi/ic_dpm_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_dpm_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xxxhdpi/ic_dpm_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_root.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xxxhdpi/ic_root.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_root_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xxxhdpi/ic_root_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_root_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/app/src/main/res/mipmap-xxxhdpi/ic_root_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #121212 4 | #F5F5F5 5 | #121212 6 | 7 | #434150 8 | #27262f 9 | #00c381 10 | #01714b 11 | #fff 12 | #fff 13 | #9900C381 14 | #BF00C381 15 | #CC00C381 16 | #E600C381 17 | #00c381 18 | 19 | @color/rail_swl_color_1 20 | @color/rail_swl_color_2 21 | @color/rail_swl_color_3 22 | @color/rail_swl_color_4 23 | @color/rail_swl_color_5 24 | 25 | 26 | 27 | #304956 28 | #446576 29 | #ffa900 30 | #865900 31 | #fff 32 | #fff 33 | #99ffa900 34 | #BFffa900 35 | #CCffa900 36 | #E6ffa900 37 | #ffa900 38 | 39 | @color/reply_swl_color_1 40 | @color/reply_swl_color_2 41 | @color/reply_swl_color_3 42 | @color/reply_swl_color_4 43 | @color/reply_swl_color_5 44 | 45 | 46 | #7d005c 47 | #57003a 48 | #f60000 49 | #8f0000 50 | #fff 51 | #fff 52 | #99f60000 53 | #BFf60000 54 | #CCf60000 55 | #E6f60000 56 | #f60000 57 | 58 | @color/crane_swl_color_1 59 | @color/crane_swl_color_2 60 | @color/crane_swl_color_3 61 | @color/crane_swl_color_4 62 | @color/crane_swl_color_5 63 | 64 | 65 | #ffd9cd 66 | #dfc3c2 67 | #48282c 68 | #301b1e 69 | #fff 70 | #48282c 71 | #99ffd9cd 72 | #BFffd9cd 73 | #CC48282c 74 | #E648282c 75 | #48282c 76 | 77 | @color/shrine_swl_color_1 78 | @color/shrine_swl_color_2 79 | @color/shrine_swl_color_3 80 | @color/shrine_swl_color_4 81 | @color/shrine_swl_color_5 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | "支持" 6 | "未安装冰箱" 7 | "冰箱的工作模式不是设备管理员模式" 8 | "未取得权限" 9 | "当前系统版本不支持静默安装" 10 | "冰箱版本过低" 11 | "未知" 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | "Supported" 6 | "Ice Box is not installed" 7 | "Ice Box's work mode is not Devie Owner" 8 | "No permissions" 9 | "Not supported by current system version" 10 | "Ice Box's version is too low" 11 | "Unknown" 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #F5F5F5 4 | #121212 5 | #43A047 6 | #F5F5F5 7 | 8 | #434150 9 | #27262f 10 | #00c381 11 | #01714b 12 | #fff 13 | #fff 14 | #9900C381 15 | #BF00C381 16 | #CC00C381 17 | #E600C381 18 | #00c381 19 | 20 | @color/rail_swl_color_1 21 | @color/rail_swl_color_2 22 | @color/rail_swl_color_3 23 | @color/rail_swl_color_4 24 | @color/rail_swl_color_5 25 | 26 | 27 | 28 | #304956 29 | #446576 30 | #ffa900 31 | #865900 32 | #fff 33 | #fff 34 | #99ffa900 35 | #BFffa900 36 | #CCffa900 37 | #E6ffa900 38 | #ffa900 39 | 40 | @color/reply_swl_color_1 41 | @color/reply_swl_color_2 42 | @color/reply_swl_color_3 43 | @color/reply_swl_color_4 44 | @color/reply_swl_color_5 45 | 46 | 47 | #7d005c 48 | #57003a 49 | #f60000 50 | #8f0000 51 | #fff 52 | #fff 53 | #99f60000 54 | #BFf60000 55 | #CCf60000 56 | #E6f60000 57 | #f60000 58 | 59 | @color/crane_swl_color_1 60 | @color/crane_swl_color_2 61 | @color/crane_swl_color_3 62 | @color/crane_swl_color_4 63 | @color/crane_swl_color_5 64 | 65 | 66 | #ffd9cd 67 | #dfc3c2 68 | #48282c 69 | #301b1e 70 | #48282c 71 | #fff 72 | #99ffd9cd 73 | #BFffd9cd 74 | #CC48282c 75 | #E648282c 76 | #48282c 77 | 78 | @color/shrine_swl_color_1 79 | @color/shrine_swl_color_2 80 | @color/shrine_swl_color_3 81 | @color/shrine_swl_color_4 82 | @color/shrine_swl_color_5 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 13 | 14 | 20 | 21 | 32 | 33 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/xml/app_delegation.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/xml/appcenter_backup_rule.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 11 | 15 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/xml/device_admin.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/xml/pref_dpm_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 11 | 14 | 18 | 19 | 20 | 21 | 24 | 27 | 28 | 29 | 30 | 33 | 36 | 37 | 38 | 39 | 43 | 46 | 49 | 53 | 54 | -------------------------------------------------------------------------------- /app/src/main/res/xml/pref_main_ui.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 12 | 15 | 18 | 19 | 20 | 21 | 26 | 29 | 32 | 35 | 36 | 37 | 38 | 43 | 46 | 49 | 52 | 53 | 54 | 55 | 60 | 63 | 64 | 65 | 66 | 71 | 74 | 77 | 78 | 79 | 80 | 85 | 88 | 89 | -------------------------------------------------------------------------------- /app/src/main/res/xml/pref_setings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 21 | 26 | 29 | 30 | 31 | 32 | 35 | 38 | 39 | 40 | 41 | 44 | 47 | 50 | 54 | 57 | 61 | 62 | 63 | 66 | 69 | 72 | 73 | -------------------------------------------------------------------------------- /app/src/miui/res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 安装狮-MIUI 3 | -------------------------------------------------------------------------------- /app/src/miui/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Install Lion-MIUI 3 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | // ext { 5 | // kotlin_version = '1.3.71' 6 | // } 7 | repositories { 8 | google() 9 | jcenter() 10 | 11 | } 12 | dependencies { 13 | classpath 'com.android.tools.build:gradle:3.6.3' 14 | // classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | 23 | maven { url "https://jitpack.io" } 24 | maven { url 'https://dl.bintray.com/heruoxin/icebox' } 25 | maven { url 'https://dl.bintray.com/rikkaw/Shizuku' } 26 | } 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | 21 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dadaewq/Install-Lion/cc0b8badbb7ee34c670d280c5c05e3d8d82eec4a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Aug 05 01:40:59 GMT+08:00 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip 7 | 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | rootProject.name = 'Install-Lion' 3 | --------------------------------------------------------------------------------