├── .gitignore ├── LICENSE ├── README.md ├── assembly ├── build.gradle ├── google-services.json ├── proguard-rules.pro └── src │ ├── complete │ ├── AndroidManifest.xml │ └── res │ │ ├── values-zh │ │ └── strings.xml │ │ ├── values │ │ └── strings.xml │ │ └── xml │ │ └── backup.xml │ └── main │ ├── AndroidManifest.xml │ └── res │ └── README ├── build.gradle ├── engine ├── build.gradle └── src │ ├── androidTest │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── oasisfeng │ │ └── island │ │ └── api │ │ └── ApiActivityTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── oasisfeng │ │ └── island │ │ ├── AdbShell.kt │ │ ├── AppUpdateReceiver.java │ │ ├── InternalActivity.java │ │ ├── IslandDeviceAdminReceiver.java │ │ ├── api │ │ ├── ApiActivity.java │ │ ├── ApiDispatcher.java │ │ └── ApiReceiver.java │ │ ├── provisioning │ │ ├── AutoIncrementalProvision.java │ │ ├── CrossProfileIntentFiltersHelper.java │ │ ├── IslandProvisioning.java │ │ ├── ManualProvisioningReceiver.java │ │ ├── ProfileOwnerManualProvisioning.java │ │ └── task │ │ │ ├── DeleteNonRequiredAppsTask.java │ │ │ ├── ProvisionLogger.java │ │ │ └── Utils.java │ │ ├── service │ │ └── IslandPersistentService.kt │ │ ├── shuttle │ │ └── ServiceShuttleActivity.java │ │ └── util │ │ ├── Cryptography.java │ │ ├── Dump.kt │ │ └── PackageManagerWrapper.java │ └── res │ ├── values-pt-rBR │ └── strings.xml │ ├── values-v24 │ └── disallowed_apps_managed_profile.xml │ ├── values-zh-rTW │ └── strings.xml │ ├── values-zh │ └── strings.xml │ ├── values │ ├── disallowed_apps_managed_device.xml │ ├── disallowed_apps_managed_profile.xml │ ├── disallowed_apps_managed_user.xml │ ├── packages_to_delete_new_managed_profile.xml │ ├── required_apps_managed_device.xml │ ├── required_apps_managed_profile.xml │ ├── required_apps_managed_user.xml │ ├── strings.xml │ ├── vendor_disallowed_apps_managed_device.xml │ ├── vendor_disallowed_apps_managed_profile.xml │ ├── vendor_disallowed_apps_managed_user.xml │ ├── vendor_required_apps_managed_device.xml │ ├── vendor_required_apps_managed_profile.xml │ └── vendor_required_apps_managed_user.xml │ └── xml │ └── device_admin.xml ├── fileprovider ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ ├── android │ │ └── content │ │ │ ├── ContentResolver.java │ │ │ └── IContentProvider.java │ └── com │ │ └── oasisfeng │ │ └── island │ │ └── fileprovider │ │ ├── ContentResolverWrapper.java │ │ ├── ExternalStorageProviderProxy.java │ │ └── ShuttleProvider.java │ └── res │ ├── values-pt-rBR │ └── strings.xml │ ├── values-zh-rTW │ └── strings.xml │ ├── values-zh │ └── strings.xml │ └── values │ └── strings.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── installer ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── oasisfeng │ │ └── island │ │ └── installer │ │ ├── AppInfoForwarderActivity.kt │ │ ├── AppInstallationNotifier.kt │ │ ├── AppInstallerActivity.java │ │ ├── AppInstallerStatusReceiver.kt │ │ ├── AppInstallerUtils.kt │ │ ├── AppSettingsHelperService.kt │ │ └── analyzer │ │ └── ApkAnalyzer.kt │ └── res │ ├── layout │ └── dialog_checkbox.xml │ ├── values-pt-rBR │ └── strings.xml │ ├── values-zh-rTW │ └── strings.xml │ ├── values-zh │ └── strings.xml │ └── values │ └── strings.xml ├── mobile ├── .gitignore ├── build.gradle ├── libs │ └── setup-wizard-lib-platform-release.aar └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── oasisfeng │ │ ├── common │ │ └── app │ │ │ ├── AppInfo.java │ │ │ ├── AppLabelCache.java │ │ │ ├── AppListProvider.java │ │ │ ├── BaseAndroidViewModel.kt │ │ │ ├── BaseAppListViewModel.java │ │ │ └── BaseAppViewModel.java │ │ ├── island │ │ ├── MainActivity.java │ │ ├── TempDebug.kt │ │ ├── action │ │ │ └── FeatureAction.kt │ │ ├── adb │ │ │ ├── AdbSecure.java │ │ │ └── ProfileRestrictionsSync.java │ │ ├── console │ │ │ └── apps │ │ │ │ └── AppListFragment.java │ │ ├── controller │ │ │ ├── IslandAppClones.kt │ │ │ └── IslandAppControl.kt │ │ ├── data │ │ │ ├── IslandAppInfo.java │ │ │ ├── IslandAppListProvider.kt │ │ │ ├── LiveProfileState.kt │ │ │ ├── LiveUserRestriction.java │ │ │ └── helper │ │ │ │ └── AppStateTrackingHelper.java │ │ ├── engine │ │ │ └── ClonedHiddenSystemApps.kt │ │ ├── featured │ │ │ ├── FeaturedListViewModel.java │ │ │ └── FeaturedViewModel.java │ │ ├── files │ │ │ └── IslandFiles.java │ │ ├── greenify │ │ │ └── GreenifyClient.java │ │ ├── guide │ │ │ └── UserGuide.java │ │ ├── model │ │ │ ├── AppListViewModel.java │ │ │ ├── AppViewModel.java │ │ │ ├── IslandViewModel.kt │ │ │ └── MainViewModel.kt │ │ ├── security │ │ │ └── SecurityPrompt.java │ │ ├── settings │ │ │ ├── AppOpsPermissionsUnlock.kt │ │ │ ├── GeneralPreferenceFragment.kt │ │ │ ├── IslandNameManager.kt │ │ │ ├── IslandSettingsActivity.kt │ │ │ ├── OpsManager.kt │ │ │ ├── Preferences.kt │ │ │ └── SettingsActivity.kt │ │ ├── setup │ │ │ ├── IslandSetup.java │ │ │ ├── SetupActivity.java │ │ │ ├── SetupViewModel.java │ │ │ └── SetupWizardFragment.java │ │ ├── shortcut │ │ │ └── IslandAppShortcut.kt │ │ ├── util │ │ │ ├── FloatingActionButtonBehavior.java │ │ │ ├── SimpleAsyncTask.java │ │ │ └── TextFormat.java │ │ └── widget │ │ │ └── PersistableSearchView.java │ │ ├── settings │ │ ├── ActionButtonPreference.java │ │ └── AdvancedPreference.java │ │ └── ui │ │ └── card │ │ └── CardViewModel.java │ └── res │ ├── animator │ ├── slide_back_in.xml │ ├── slide_back_out.xml │ ├── slide_next_in.xml │ └── slide_next_out.xml │ ├── color │ └── chip_background_color.xml │ ├── drawable-nodpi │ ├── ic_launcher_appops.webp │ ├── ic_launcher_fx.webp │ ├── ic_launcher_greenify.webp │ └── ic_launcher_saf_enhancer.webp │ ├── drawable-xxxhdpi │ └── setup_header.9.png │ ├── drawable │ ├── app_icon_empty.xml │ ├── bottom_navigation_text_color.xml │ ├── ic_add_to_photos_24dp.xml │ ├── ic_explore_black_24dp.xml │ ├── ic_filter_list_white_24dp.xml │ ├── ic_info_black_24dp.xml │ ├── ic_island_black_24dp.xml │ ├── ic_launch_24dp.xml │ ├── ic_lightbulb_outline_24dp.xml │ ├── ic_lock_24dp.xml │ ├── ic_mainland_black_24dp.xml │ ├── ic_search_white_24dp.xml │ ├── ic_settings_black_24dp.xml │ └── ic_unlock_24dp.xml │ ├── layout │ ├── activity_main.xml │ ├── app_entry.xml │ ├── app_list.xml │ ├── card.xml │ ├── featured_entry.xml │ ├── preference_widget_action_button.xml │ └── setup_wizard.xml │ ├── menu │ ├── app_actions.xml │ ├── main_actions.xml │ └── pref_island_actions.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_background.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── values-night │ └── colors.xml │ ├── values-pt-rBR │ └── strings.xml │ ├── values-zh-rTW │ └── strings.xml │ ├── values-zh │ └── strings.xml │ ├── values │ ├── colors.xml │ ├── constants.xml │ ├── dimens.xml │ ├── ids.xml │ ├── preferences.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ ├── actions.xml │ ├── pref_about.xml │ ├── pref_general.xml │ ├── pref_headers.xml │ ├── pref_island.xml │ └── searchable.xml ├── open ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ ├── android │ │ ├── app │ │ │ ├── DerivedAppOpsManager.java │ │ │ └── admin │ │ │ │ └── DerivedDevicePolicyManager.java │ │ └── content │ │ │ └── DerivedRestrictionsManager.java │ └── com │ │ └── oasisfeng │ │ └── island │ │ ├── ApiConstants.java │ │ ├── DelegatedScopeAuthorization.kt │ │ ├── DelegationManager.kt │ │ ├── RestrictedBinderProxy.java │ │ ├── SystemServiceBridge.java │ │ └── api │ │ ├── DelegatedAppOpsManager.java │ │ ├── DelegatedDevicePolicyManager.java │ │ ├── DerivedManagerHelper.java │ │ └── PermissionForwardingRestrictionsManager.java │ └── res │ ├── values-pt-rBR │ └── strings.xml │ ├── values-zh-rTW │ └── strings.xml │ ├── values-zh │ └── strings.xml │ └── values │ └── strings.xml ├── settings.gradle ├── shared ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── aidl │ └── com │ │ └── oasisfeng │ │ └── island │ │ └── shuttle │ │ ├── IMethodShuttle.aidl │ │ ├── IServiceConnection.aidl │ │ ├── IUnbinder.aidl │ │ └── MethodInvocation.aidl │ ├── java │ ├── android │ │ └── app │ │ │ └── NotificationManagerExtender.java │ └── com │ │ └── oasisfeng │ │ ├── android │ │ └── content │ │ │ ├── IntentCompat.java │ │ │ └── pm │ │ │ ├── ComponentInfo.kt │ │ │ └── PackageManagerUtils.kt │ │ ├── island │ │ ├── Config.java │ │ ├── IslandApplication.java │ │ ├── PersistentService.kt │ │ ├── analytics │ │ │ ├── Analytics.kt │ │ │ ├── AnalyticsImpl.kt │ │ │ └── CrashReport.java │ │ ├── api │ │ │ └── Api.java │ │ ├── appops │ │ │ ├── AppOpsCompat.java │ │ │ └── AppOpsHelper.kt │ │ ├── data │ │ │ └── helper │ │ │ │ └── ApplicationInfoExtensions.kt │ │ ├── engine │ │ │ ├── CrossProfile.kt │ │ │ ├── IslandManager.java │ │ │ └── common │ │ │ │ └── WellKnownPackages.java │ │ ├── firebase │ │ │ └── FirebaseWrapper.java │ │ ├── installer │ │ │ └── InstallerExtras.java │ │ ├── notification │ │ │ ├── NotificationIds.java │ │ │ └── NotificationIds.kt │ │ ├── provisioning │ │ │ ├── CriticalAppsManager.java │ │ │ └── SystemAppsManager.java │ │ ├── settings │ │ │ └── IslandSettings.kt │ │ ├── shortcut │ │ │ └── ShortcutIcons.java │ │ ├── shuttle │ │ │ ├── ActivityShuttle.java │ │ │ ├── Closure.kt │ │ │ ├── ContextShuttle.java │ │ │ ├── MethodInvocation.java │ │ │ ├── MethodShuttle.java │ │ │ ├── ServiceShuttle.java │ │ │ ├── ServiceShuttleContext.java │ │ │ ├── Shuttle.kt │ │ │ ├── ShuttleCarrierActivity.kt │ │ │ ├── ShuttleProvider.kt │ │ │ └── ShuttleServiceConnection.java │ │ └── util │ │ │ ├── CallerAwareActivity.java │ │ │ ├── DeviceAdmins.java │ │ │ ├── DevicePolicies.java │ │ │ ├── DevicePolicies.kt │ │ │ ├── Hacks.java │ │ │ ├── Modules.java │ │ │ ├── OwnerUser.java │ │ │ ├── Permissions.java │ │ │ ├── ProfileUser.java │ │ │ ├── RomVariants.kt │ │ │ ├── UserHandles.kt │ │ │ └── Users.java │ │ ├── perf │ │ ├── Performances.java │ │ ├── Stopwatch.java │ │ └── Ticker.java │ │ └── settings │ │ ├── AppSettings.kt │ │ └── AppSettingsProvider.kt │ └── res │ ├── drawable │ ├── ic_landscape_black_24dp.xml │ └── ic_settings_applications_white_24dp.xml │ ├── values-night │ └── colors.xml │ ├── values-pt-rBR │ └── strings.xml │ ├── values-v24 │ └── flags.xml │ ├── values-v26 │ └── flags.xml │ ├── values-v28 │ └── flags.xml │ ├── values-zh-rTW │ └── strings.xml │ ├── values-zh │ └── strings.xml │ ├── values │ ├── colors.xml │ ├── flags.xml │ ├── settings.xml │ └── strings.xml │ └── xml │ ├── analytics_tracker.xml │ └── config_defaults.xml └── watcher ├── build.gradle └── src └── main ├── AndroidManifest.xml ├── java └── com │ └── oasisfeng │ └── island │ └── watcher │ ├── IslandAppWatcher.kt │ └── IslandWatcher.kt └── res ├── values-pt-rBR └── strings.xml ├── values-zh-rTW └── strings.xml ├── values-zh └── strings.xml └── values └── strings.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Local directories 2 | /release 3 | /captures 4 | /proguard 5 | /design 6 | /dist 7 | 8 | # Local configuration file (sdk path, etc) 9 | /local.properties 10 | /keystore.jks 11 | 12 | # Android Studio 13 | .idea 14 | *.iml 15 | .gradle 16 | local.gradle 17 | build 18 | 19 | # OSX files 20 | .DS_Store 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Island 2 | 3 | Island for Android 4 | 5 | ## Build Instructions 6 | 7 | Island depends on ["deagle" library](https://github.com/oasisfeng/deagle), which must be cloned alongside Island in the same path. 8 | 9 | ``` 10 | \-- 11 | \- island 12 | \- deagle 13 | ``` 14 | 15 | This project is constructed into several modules, with **assembly** module as the build portal, 16 | to support separate "light" build for core modules, in the form of "product flavor" in Gradle build configuration. 17 | 18 | The **"engine"** module shares the same package name with the **"complete"** build, to inherit the profile/device owner privilege. 19 | The **"mobile"** and other modules can be installed and updated separately alongside **"engine"** module for development convenience. 20 | 21 | ## Open API 22 | 23 | Due to the exclusivity nature, user could only use one Android DPC app at a time, and price of switching DPC is far too heavy. To encourage active exploration and broader development in the capabilities of DPC and therefore better benefit users, 24 | Island is devoted to build an open collaboration for community developers, either in development of this project or opening DPC capabilities to 3rd-party apps via open API. Island itself will not focus on the rich set of features, but mainly focuses on building a powerful **engine** as an open platform for much more apps from the community. 25 | 26 | Starting from the first public version of Island, all APIs are open to 3rd-party apps with the standard runtime-permission of Android as user authorization. Developers can start building apps now to take advantage of the Island open APIs. 27 | 28 | The protocol of all APIs are well defined and maintained in the **[class "Api"](/shared/src/main/java/com/oasisfeng/island/api/Api.java)**. 29 | 30 | ## Contribution 31 | 32 | If you found bugs, made minor improvements or translated the strings, please feel free to send us pull-requests. 33 | 34 | If you are interested in improving the functionality of Island, please create an issue first to discuss your thoughts with us, we are open to collaboration in future development. 35 | 36 | If you need new APIs for your apps to take advantage of the DPC capabilities, please feel free to create an issue to describe your app and its use case of those APIs. We are still in the early stage of building a rich set of open APIs. 37 | -------------------------------------------------------------------------------- /assembly/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'com.google.gms.google-services' 3 | apply plugin: 'com.google.firebase.crashlytics' 4 | 5 | android { 6 | compileSdkVersion this.compileSdkVersion 7 | 8 | defaultConfig { 9 | applicationId "com.oasisfeng.island" 10 | minSdkVersion this.minSdkVersion 11 | targetSdkVersion 29 // Private APIs are accessed by module "open" and "fileprovider" (via indirectly loaded remote class) 12 | resConfigs "en", "zh", "zh-rTW" 13 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 14 | } 15 | 16 | buildFeatures.dataBinding true 17 | 18 | buildTypes { 19 | debug { 20 | firebaseCrashlytics.mappingFileUploadEnabled false 21 | } 22 | release { 23 | minifyEnabled true 24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 25 | } 26 | } 27 | 28 | flavorDimensions "packaging" 29 | 30 | productFlavors { 31 | complete { 32 | dimension "packaging" 33 | } 34 | engine { // Use the same application ID to retain the profile / device ownership between "engine" and "full" build. 35 | dimension "packaging" 36 | matchingFallbacks = ['full'] 37 | } 38 | mobile { 39 | applicationIdSuffix ".mobile" 40 | dimension "packaging" 41 | matchingFallbacks = ['full'] 42 | } 43 | fileprovider { 44 | applicationIdSuffix ".fileprovider" 45 | dimension "packaging" 46 | matchingFallbacks = ['full'] 47 | } 48 | } 49 | 50 | compileOptions { 51 | sourceCompatibility JavaVersion.VERSION_1_8 52 | targetCompatibility JavaVersion.VERSION_1_8 53 | } 54 | 55 | lintOptions { 56 | check 'NewApi' 57 | abortOnError true 58 | htmlReport false 59 | xmlReport false 60 | textReport true 61 | textOutput "stdout" 62 | } 63 | 64 | repositories.flatDir { 65 | dirs '../app/libs' 66 | } 67 | } 68 | 69 | dependencies { 70 | // Complete 71 | completeImplementation project(':engine') 72 | completeImplementation project(':mobile') 73 | completeImplementation project(':fileprovider') 74 | completeImplementation project(':installer') 75 | completeImplementation project(':watcher') 76 | completeImplementation project(':open') 77 | // Engine only 78 | engineImplementation project(':engine') 79 | // Mobile only 80 | mobileImplementation project(':mobile') 81 | // File Provider only 82 | fileproviderImplementation project(':fileprovider') 83 | } 84 | -------------------------------------------------------------------------------- /assembly/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in E:\Android\SDK/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Shrink only 20 | -dontobfuscate 21 | 22 | # For AOSP internal disclosure in module "fileprovider" 23 | -keepclassmembers class * extends android.content.ContentResolver { *; } 24 | -dontwarn android.content.ContentResolver 25 | -dontwarn android.content.IContentProvider 26 | 27 | # Remove verbose and debug logging 28 | -assumenosideeffects class android.util.Log { 29 | public static boolean isLoggable(java.lang.String, int); 30 | public static int v(...); 31 | } 32 | 33 | # For generics reflection to work 34 | -keepattributes Signature 35 | -keepattributes *Annotation* 36 | 37 | # More debugging info (line number) 38 | -renamesourcefileattribute SourceFile 39 | -keepattributes SourceFile,LineNumberTable 40 | -------------------------------------------------------------------------------- /assembly/src/complete/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assembly/src/complete/res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 炼妖壶 4 | -------------------------------------------------------------------------------- /assembly/src/complete/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Island 4 | -------------------------------------------------------------------------------- /assembly/src/complete/res/xml/backup.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assembly/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /assembly/src/main/res/README: -------------------------------------------------------------------------------- 1 | Just a placeholder to keep this folder in repo for Crashlytics to build correctly. -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.4.32' 5 | ext.kotlin_coroutine_version = '1.3.9' 6 | repositories { 7 | google() 8 | jcenter() 9 | } 10 | dependencies { 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | classpath 'com.android.tools.build:gradle:4.1.3' 13 | classpath 'com.google.gms:google-services:4.3.4' 14 | classpath 'com.google.firebase:firebase-crashlytics-gradle:2.4.1' 15 | 16 | // NOTE: Do not place your application dependencies here; they belong 17 | // in the individual module build.gradle files 18 | } 19 | } 20 | 21 | if (file('local.gradle').exists()) apply from: 'local.gradle' 22 | 23 | allprojects { 24 | repositories { 25 | google() 26 | jcenter() 27 | } 28 | 29 | ext.compileSdkVersion = 30 30 | ext.minSdkVersion = 24 31 | } 32 | -------------------------------------------------------------------------------- /engine/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | compileSdkVersion this.compileSdkVersion 6 | 7 | defaultConfig { 8 | minSdkVersion this.minSdkVersion 9 | 10 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 11 | } 12 | 13 | kotlinOptions.jvmTarget = "1.8" 14 | 15 | compileOptions { 16 | sourceCompatibility JavaVersion.VERSION_1_8 17 | targetCompatibility JavaVersion.VERSION_1_8 18 | } 19 | 20 | lintOptions { 21 | check 'NewApi' 22 | abortOnError false 23 | xmlReport false 24 | textReport true 25 | textOutput "stdout" 26 | } 27 | } 28 | 29 | dependencies { 30 | implementation project(':shared') 31 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" 32 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutine_version" 33 | implementation 'androidx.core:core-ktx:1.3.2' // ShortcutShuttle 34 | 35 | androidTestImplementation 'androidx.test:runner:1.3.0' 36 | androidTestImplementation 'androidx.test:rules:1.3.0' 37 | } -------------------------------------------------------------------------------- /engine/src/androidTest/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /engine/src/main/java/com/oasisfeng/island/AppUpdateReceiver.java: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.island; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | import com.oasisfeng.island.provisioning.IslandProvisioning; 8 | import com.oasisfeng.island.util.Users; 9 | 10 | /** 11 | * Handle {@link Intent#ACTION_MY_PACKAGE_REPLACED} 12 | * 13 | * Created by Oasis on 2017/7/20. 14 | */ 15 | public class AppUpdateReceiver extends BroadcastReceiver { 16 | 17 | @Override public void onReceive(final Context context, final Intent intent) { 18 | // Currently, just blindly start the device owner provisioning, since it is idempotent, at least at present. 19 | if (Intent.ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction())) 20 | if (Users.isOwner()) IslandProvisioning.startOwnerUserPostProvisioningIfNeeded(context); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /engine/src/main/java/com/oasisfeng/island/InternalActivity.java: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.island; 2 | 3 | import com.oasisfeng.island.shuttle.ServiceShuttleActivity; 4 | 5 | /** 6 | * Placeholder for activity declaration, to keep stable exported entrances (especially important for shortcut) and allow internal refactoring. 7 | * 8 | * Created by Oasis on 2017/9/21. 9 | */ 10 | public class InternalActivity { 11 | 12 | public static final class _1 extends ServiceShuttleActivity {} 13 | } 14 | -------------------------------------------------------------------------------- /engine/src/main/java/com/oasisfeng/island/IslandDeviceAdminReceiver.java: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.island; 2 | 3 | import android.app.admin.DeviceAdminReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | import com.oasisfeng.island.provisioning.IslandProvisioning; 8 | import com.oasisfeng.island.util.ProfileUser; 9 | 10 | import static android.os.Build.VERSION.SDK_INT; 11 | import static android.os.Build.VERSION_CODES.O; 12 | 13 | /** 14 | * Handles events related to managed profile. 15 | */ 16 | public class IslandDeviceAdminReceiver extends DeviceAdminReceiver { 17 | 18 | @ProfileUser @Override public void onProfileProvisioningComplete(final Context context, final Intent intent) { 19 | // DevicePolicyManager.ACTION_PROVISIONING_SUCCESSFUL is used instead of this trigger on Android O+. 20 | if (SDK_INT < O) IslandProvisioning.onProfileProvisioningComplete(context, intent); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /engine/src/main/java/com/oasisfeng/island/api/ApiReceiver.java: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.island.api; 2 | 3 | import android.app.Activity; 4 | import android.content.BroadcastReceiver; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.util.Log; 8 | 9 | import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; 10 | 11 | /** 12 | * Broadcast API invocation handler. Result will be delivered as broadcast result in ordered broadcast. 13 | * 14 | * Created by Oasis on 2017/9/18. 15 | */ 16 | public class ApiReceiver extends BroadcastReceiver { 17 | 18 | @Override public void onReceive(final Context context, final Intent intent) { 19 | Log.i(TAG, "API request: " + intent.toUri(0)); 20 | if ((intent.getFlags() & FLAG_RECEIVER_FOREGROUND) == 0) Log.w(TAG, "Add Intent.FLAG_RECEIVER_FOREGROUND to API intent for less delay."); 21 | 22 | String result = ApiDispatcher.verifyCaller(context, intent, null, -1); 23 | if (result != null) { 24 | setResultIfOrdered(Api.latest.RESULT_UNVERIFIED_IDENTITY, result); 25 | Log.w(TAG, "Caller verification failure: " + result); 26 | return; 27 | } 28 | result = ApiDispatcher.dispatch(context, intent); 29 | if (result == null) { 30 | setResultIfOrdered(Activity.RESULT_OK, null); 31 | Log.i(TAG, "API result: Success"); 32 | } else { 33 | setResultIfOrdered(Activity.RESULT_CANCELED, result); 34 | Log.i(TAG, "API result: " + result); 35 | } 36 | } 37 | 38 | private void setResultIfOrdered(final int result, final String message) { 39 | if (isOrderedBroadcast()) setResult(result, message, null); 40 | } 41 | 42 | private static final String TAG = "API.Receiver"; 43 | } 44 | -------------------------------------------------------------------------------- /engine/src/main/java/com/oasisfeng/island/provisioning/AutoIncrementalProvision.java: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.island.provisioning; 2 | 3 | import com.oasisfeng.android.os.Loopers; 4 | import com.oasisfeng.island.engine.BuildConfig; 5 | import com.oasisfeng.island.util.ProfileUser; 6 | import com.oasisfeng.island.util.Users; 7 | import com.oasisfeng.pattern.PseudoContentProvider; 8 | import com.oasisfeng.perf.Performances; 9 | import com.oasisfeng.perf.Stopwatch; 10 | 11 | /** 12 | * Perform incremental provision 13 | * 14 | * Created by Oasis on 2017/11/21. 15 | */ 16 | public class AutoIncrementalProvision extends PseudoContentProvider { 17 | 18 | @Override public boolean onCreate() { 19 | final Stopwatch stopwatch = Performances.startUptimeStopwatch(); 20 | if (Users.isOwner()) { 21 | Loopers.addIdleTask(() -> IslandProvisioning.startOwnerUserPostProvisioningIfNeeded(context())); 22 | } else if (Users.isProfileManagedByIsland()) { // False if profile is not enabled yet. (during the broadcast ACTION_PROFILE_PROVISIONING_COMPLETE) 23 | final Thread thread = new Thread(this::startInProfile); 24 | thread.setPriority(Thread.MIN_PRIORITY); 25 | thread.start(); 26 | } 27 | if (BuildConfig.DEBUG) Performances.check(stopwatch, 5, "IncPro.MainThread"); 28 | return false; 29 | } 30 | 31 | @ProfileUser private void startInProfile() { 32 | final Stopwatch stopwatch = Performances.startUptimeStopwatch(); 33 | IslandProvisioning.performIncrementalProfileOwnerProvisioningIfNeeded(context()); 34 | if (BuildConfig.DEBUG) Performances.check(stopwatch, 10, "IncPro.WorkerThread"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /engine/src/main/java/com/oasisfeng/island/provisioning/ManualProvisioningReceiver.java: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.island.provisioning; 2 | 3 | import android.app.admin.DevicePolicyManager; 4 | import android.content.BroadcastReceiver; 5 | import android.content.ComponentName; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.util.Log; 9 | 10 | import com.oasisfeng.island.util.DevicePolicies; 11 | import com.oasisfeng.island.util.Users; 12 | 13 | import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; 14 | import static android.content.pm.PackageManager.DONT_KILL_APP; 15 | 16 | /** 17 | * Receiver for starting post-provisioning procedure for manual provisioning. 18 | * 19 | * The enabled state of this receiver in managed profile also serves as an indication of pending manual provisioning. 20 | * 21 | * Created by Oasis on 2017/4/8. 22 | */ 23 | public class ManualProvisioningReceiver extends BroadcastReceiver { // Full class name must start with "com.oasisfeng.island.provision", see MainActivity.onCreateInProfile() 24 | 25 | @Override public void onReceive(final Context context, final Intent intent) { 26 | final String action = intent.getAction(); 27 | if (action == null || Intent.ACTION_USER_INITIALIZE.equals(action)) { 28 | if (Users.isOwner()) return; // Should never happen 29 | if (Users.profile == null) { 30 | Log.d(TAG, "Profile is disabled"); // Profile is not enabled yet, that means we are currently in the managed provisioning flow 31 | return; // Nothing needs to be done here, we will receive ACTION_PROFILE_PROVISIONING_COMPLETE soon. 32 | } 33 | if (! Users.isProfileManagedByIsland()) { 34 | Log.d(TAG, "Not profile owner"); 35 | return; 36 | } 37 | Log.i(TAG, (action != null ? "User initialized: " : "Provisioning resumed: ") + Users.toId(android.os.Process.myUserHandle())); 38 | IslandProvisioning.start(context, action); 39 | 40 | context.getPackageManager().setComponentEnabledSetting(new ComponentName(context, getClass()), COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP); // Disable self 41 | } else if (DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED.equals(action)){ 42 | Log.i(TAG, "Device owner changed."); 43 | if (new DevicePolicies(context).isActiveDeviceOwner()) IslandProvisioning.start(context, action); 44 | } 45 | } 46 | 47 | private static final String TAG = ManualProvisioningReceiver.class.getSimpleName(); 48 | } 49 | -------------------------------------------------------------------------------- /engine/src/main/java/com/oasisfeng/island/provisioning/ProfileOwnerManualProvisioning.java: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.island.provisioning; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import com.oasisfeng.island.provisioning.task.DeleteNonRequiredAppsTask; 7 | import com.oasisfeng.island.util.DevicePolicies; 8 | import com.oasisfeng.island.util.ProfileUser; 9 | import com.oasisfeng.island.util.Users; 10 | 11 | import static com.oasisfeng.island.provisioning.task.DeleteNonRequiredAppsTask.PROFILE_OWNER; 12 | 13 | /** 14 | * Simulate the managed provisioning procedure for manually enabled managed profile. 15 | * 16 | * Created by Oasis on 2016/4/18. 17 | */ 18 | class ProfileOwnerManualProvisioning { 19 | 20 | @ProfileUser static void start(final Context context, final DevicePolicies policies) { 21 | new DeleteNonRequiredAppsTask(context, context.getPackageName(), PROFILE_OWNER, true, Users.toId(Users.current()), false, new DeleteNonRequiredAppsTask.Callback() { 22 | @Override public void onSuccess() {} 23 | @Override public void onError() { Log.e(TAG, "Error provisioning (task 1)"); } 24 | }).run(); 25 | 26 | /* DisableBluetoothSharingTask & DisableInstallShortcutListenersTask cannot be done here, since they disable components. */ 27 | /* Settings.Secure.MANAGED_PROFILE_CONTACT_REMOTE_SEARCH can be toggled in system Settings - Users & Profiles - Profile Settings */ 28 | /* DISALLOW_WALLPAPER cannot be changed by profile / device owner. */ 29 | 30 | // Set default cross-profile intent-filters 31 | CrossProfileIntentFiltersHelper.setFilters(policies); 32 | } 33 | 34 | private static final String TAG = ProfileOwnerManualProvisioning.class.getSimpleName(); 35 | } 36 | -------------------------------------------------------------------------------- /engine/src/main/java/com/oasisfeng/island/provisioning/task/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014, The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.oasisfeng.island.provisioning.task; 18 | 19 | import android.content.pm.ApplicationInfo; 20 | import android.content.pm.PackageManager; 21 | import android.os.RemoteException; 22 | 23 | import com.oasisfeng.island.provisioning.task.DeleteNonRequiredAppsTask.IPackageManager; 24 | 25 | import java.util.HashSet; 26 | import java.util.List; 27 | import java.util.Set; 28 | 29 | /** 30 | * Class containing various auxiliary methods. 31 | */ 32 | class Utils { 33 | 34 | public Utils() {} 35 | 36 | /** 37 | * Returns the currently installed system apps on a given user. 38 | * 39 | *

Calls into the {@link IPackageManager} to retrieve all installed packages on the given 40 | * user and returns the package names of all system apps. 41 | * 42 | * @param ipm an {@link IPackageManager} object 43 | * @param userId the id of the user we are interested in 44 | */ 45 | public Set getCurrentSystemApps(IPackageManager ipm, int userId) { 46 | Set apps = new HashSet(); 47 | List aInfos = null; 48 | try { 49 | aInfos = ipm.getInstalledApplications( 50 | PackageManager.GET_UNINSTALLED_PACKAGES, userId).getList(); 51 | } catch (RemoteException neverThrown) { 52 | ProvisionLogger.loge("This should not happen.", neverThrown); 53 | } 54 | for (ApplicationInfo aInfo : aInfos) { 55 | if ((aInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 56 | apps.add(aInfo.packageName); 57 | } 58 | } 59 | return apps; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /engine/src/main/java/com/oasisfeng/island/util/Dump.kt: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.island.util 2 | 3 | import android.os.Debug 4 | import android.os.ParcelFileDescriptor 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.withContext 7 | import kotlinx.coroutines.withTimeout 8 | 9 | /** 10 | * Helper for dumping system service 11 | * 12 | * Created by Oasis on 2019-6-24. 13 | */ 14 | object Dump { 15 | 16 | suspend fun systemService(service: String, vararg args: String, timeout: Long = 5_000, processor: (Sequence) -> T): T? { 17 | val pipe = ParcelFileDescriptor.createPipe() 18 | pipe[1].use { 19 | if (! Debug.dumpService(service, it.fileDescriptor, if (args.isEmpty()) null else args)) return null 20 | return withContext(Dispatchers.IO) { // IO thread to receive dump to avoid infinite blocking in large dump. 21 | withTimeout(timeout) { 22 | ParcelFileDescriptor.AutoCloseInputStream(pipe[0]).bufferedReader().useLines(processor) 23 | } 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /engine/src/main/res/values-pt-rBR/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Controle apps do Island 4 | Controle apps gerenciados pelo Island 5 | Suspenda apps gerenciados pelo Island 6 | Este App pode suspender ou ativar qualquer app gerenciado pelo Island a qualquer hora. 7 | Execute apps gerenciados pelo Island 8 | Este App pode executar qualquer app gerenciado pelo Island, que será ativado se for necessário. 9 | Suspenda apps gerenciados pelo Island 10 | Este App pode suspender ou ativar qualquer app gerenciado pelo Island a qualquer hora. 11 | 12 | Disponibilizando Island… 13 | Disponibilizando Mainland… 14 | Pode levar alguns minutos. 15 | 16 | O atalho não é mais válido, favor verificar o status ou recriá-lo no Island 17 | A configuração do Island está concluída. 18 | Conserto feito. 19 | 20 | -------------------------------------------------------------------------------- /engine/src/main/res/values-v24/disallowed_apps_managed_profile.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /engine/src/main/res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 煉妖壺管理的應用程式 4 | 控制煉妖壺管理的應用程式 5 | 凍結煉妖壺管理的應用程式 6 | 這個應用程式被授權可隨時凍結及解凍煉妖壺管理的任意應用程式 7 | 啟動煉妖壺管理的應用程式 8 | 這個應用程式被授權可啟動煉妖壺管理的任何應用程式,凍結的應用程式将在啟動前解凍。 9 | 暫停煉妖壺管理的應用程式 10 | 這個應用程式被授權可隨時暫停及恢復煉妖壺管理的任意應用程式 11 | 12 | 正在準備 壺中界… 13 | 正在接管 界外… 14 | 可能需要數分鐘之久 15 | 16 | 快速啟動方式已失效,請檢查壺中界的狀態或重新建立此快速啟動方式。 17 | 壺中界已建立完成。 18 | 已完成回爐修整 19 | -------------------------------------------------------------------------------- /engine/src/main/res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 炼妖壶管理的应用 4 | 控制炼妖壶管理的应用 5 | 冻结炼妖壶管理的应用 6 | 这个应用被授权可随时冻结及解冻炼妖壶管理的任意应用 7 | 启动炼妖壶管理的应用 8 | 这个应用被授权可启动炼妖壶管理的任何应用,冻结的应用将在启动前解冻。 9 | 暂停炼妖壶管理的应用 10 | 这个应用被授权可随时暂停及恢复炼妖壶管理的任意应用 11 | 12 | 正在准备 壶中界… 13 | 正在接管 界外… 14 | 可能需要数分钟之久 15 | 16 | 快捷方式已失效,请检查壶中界的状态或重新创建此快捷方式。 17 | 壶中界已完成创建。 18 | 已完成回炉修整 19 | -------------------------------------------------------------------------------- /engine/src/main/res/values/disallowed_apps_managed_device.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /engine/src/main/res/values/disallowed_apps_managed_profile.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | com.android.server.telecom 23 | 24 | 25 | -------------------------------------------------------------------------------- /engine/src/main/res/values/disallowed_apps_managed_user.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /engine/src/main/res/values/packages_to_delete_new_managed_profile.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | com.android.server.telecom 23 | 24 | 25 | -------------------------------------------------------------------------------- /engine/src/main/res/values/required_apps_managed_device.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 22 | 23 | com.android.settings 24 | com.android.contacts 25 | com.android.dialer 26 | com.android.stk 27 | com.android.providers.downloads 28 | com.android.providers.downloads.ui 29 | com.android.documentsui 30 | 31 | 32 | -------------------------------------------------------------------------------- /engine/src/main/res/values/required_apps_managed_profile.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 22 | 23 | com.android.contacts 24 | com.android.settings 25 | com.android.providers.downloads 26 | com.android.providers.downloads.ui 27 | com.android.documentsui 28 | 29 | 30 | -------------------------------------------------------------------------------- /engine/src/main/res/values/required_apps_managed_user.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 22 | 23 | com.android.settings 24 | com.android.contacts 25 | com.android.dialer 26 | com.android.stk 27 | com.android.providers.downloads 28 | com.android.providers.downloads.ui 29 | com.android.documentsui 30 | 31 | 32 | -------------------------------------------------------------------------------- /engine/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Control Island apps 4 | control apps managed by Island 5 | Freeze Island-managed apps 6 | This app can freeze and unfreeze any app managed by Island at any time. 7 | Launch Island-managed apps 8 | This app can launch any app managed by Island, which will be unfrozen if needed. 9 | Suspend Island-managed apps 10 | This app can suspend and unsuspend any app managed by Island at any time. 11 | 12 | Provisioning Island… 13 | Provisioning Mainland… 14 | It may take several minutes. 15 | 16 | Shortcut is no longer valid, please check the status of Island or re-create it in Island 17 | Island setup is complete. 18 | Done repairing. 19 | -------------------------------------------------------------------------------- /engine/src/main/res/values/vendor_disallowed_apps_managed_device.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /engine/src/main/res/values/vendor_disallowed_apps_managed_profile.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /engine/src/main/res/values/vendor_disallowed_apps_managed_user.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /engine/src/main/res/values/vendor_required_apps_managed_device.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /engine/src/main/res/values/vendor_required_apps_managed_profile.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /engine/src/main/res/values/vendor_required_apps_managed_user.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /engine/src/main/res/xml/device_admin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /fileprovider/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion this.compileSdkVersion 5 | 6 | defaultConfig { 7 | minSdkVersion this.minSdkVersion 8 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 9 | } 10 | 11 | compileOptions { 12 | sourceCompatibility JavaVersion.VERSION_1_8 13 | targetCompatibility JavaVersion.VERSION_1_8 14 | } 15 | } 16 | 17 | android.libraryVariants.all { variant -> 18 | String taskName = String.format("remove%sStubClasses", variant.name.capitalize()) 19 | task "${taskName}" { 20 | doLast { delete "${buildDir}/intermediates/classes/${variant.name}" + "/android" } 21 | } 22 | variant.processJavaResources.dependsOn(taskName) 23 | } 24 | 25 | dependencies { 26 | implementation project(':shared') 27 | implementation 'androidx.annotation:annotation:1.1.0' 28 | 29 | testImplementation 'junit:junit:4.12' 30 | androidTestImplementation 'androidx.test:runner:1.3.0' 31 | } 32 | -------------------------------------------------------------------------------- /fileprovider/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /fileprovider/src/main/java/android/content/IContentProvider.java: -------------------------------------------------------------------------------- 1 | package android.content; 2 | 3 | import android.os.IInterface; 4 | import androidx.annotation.Keep; 5 | import androidx.annotation.RestrictTo; 6 | 7 | /** 8 | * Stub class for real IContentProvider, only for compilation purpose. 9 | * 10 | * Created by Oasis on 2017/4/11. 11 | */ 12 | @Keep @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 13 | public interface IContentProvider extends IInterface { 14 | } 15 | -------------------------------------------------------------------------------- /fileprovider/src/main/res/values-pt-rBR/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Cruzamento pelo Island 3 | 4 | -------------------------------------------------------------------------------- /fileprovider/src/main/res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 煉妖壺·彼界 3 | 4 | -------------------------------------------------------------------------------- /fileprovider/src/main/res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 炼妖壶·彼界 3 | 4 | -------------------------------------------------------------------------------- /fileprovider/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Shuttled by Island 3 | 4 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | android.enableJetifier=true 21 | android.useAndroidX=true 22 | 23 | # Disable instrumentation by Firebase Performance 24 | firebasePerformanceInstrumentationEnabled=false 25 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Sep 08 16:01:04 CST 2020 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-6.5-all.zip 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /installer/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-parcelize' 4 | 5 | android { 6 | compileSdkVersion this.compileSdkVersion 7 | 8 | defaultConfig { 9 | minSdkVersion this.minSdkVersion 10 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 11 | } 12 | 13 | kotlinOptions.jvmTarget = "1.8" 14 | 15 | compileOptions { 16 | sourceCompatibility JavaVersion.VERSION_1_8 17 | targetCompatibility JavaVersion.VERSION_1_8 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation project(':shared') 23 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" 24 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$kotlin_coroutine_version" 25 | implementation "com.jaredrummler:apk-parser:1.0.2" 26 | 27 | testImplementation 'junit:junit:4.12' 28 | androidTestImplementation 'androidx.test:runner:1.3.0' 29 | } 30 | -------------------------------------------------------------------------------- /installer/src/main/res/layout/dialog_checkbox.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /installer/src/main/res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 應用程式安裝器 4 | 5 | “%1$s”請求安裝 %2$s。\n\n是否繼續? 6 | “%1$s”請求更新 %2$s。\n\n是否繼續? 7 | “%1$s”請求複製 %2$s。\n\n是否繼續? 8 | “%1$s” (%2$s) 版本 %3$s (%4$d) 9 | “%1$s” (%2$s) 10 | 應用程式 11 | 始終允許此來源安裝應用程式 12 | 安裝失敗:%s 13 | Android 系统的應用程式安裝過程發生了內部錯誤 14 | “%1$s”正在安裝 %2$s 15 | “%1$s”正在更新 %2$s 16 | “%1$s”正在複製 %2$s 17 | 18 | “%1$s”已安裝新應用程式“%2$s” 19 | “%1$s”已更新“%2$s” 20 | \"%1$s\"已更新自己 21 | 已取得的權限:%s 22 | 適配 Android 版本: %s 23 | 適配 Android: %1$s -> %2$s 24 | 應用程式設定 25 | 26 | 在哪一個應用程式中顯示關於 %1$s (%2$s) 的訊息 27 | 點擊“強制停止”可打開其它工具中針對此應用程式的設定 28 | 嘗試傳統安裝方式 29 | 此應用程式含有附加組件,可能無法以這種方式正常複製。如果遇到故障,請嘗試重新從應用程式商店安裝。 30 | 31 | 可能是“MIUI 優化”導致(可在“系统設定 - 開發人員選項”中關閉) 32 | -------------------------------------------------------------------------------- /installer/src/main/res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 应用安装器 4 | 5 | “%1$s”请求安装 %2$s。\n\n是否继续? 6 | “%1$s”请求更新 %2$s。\n\n是否继续? 7 | “%1$s”请求克隆 %2$s。\n\n是否继续? 8 | “%1$s” (%2$s) 版本 %3$s (%4$d) 9 | “%1$s” (%2$s) 10 | 应用 11 | 始终允许此来源安装应用 12 | 安装失败:%s 13 | Android 系统的应用安装过程发生了内部错误 14 | 应用安装中止 15 | “%1$s”正在安装 %2$s 16 | “%1$s”正在更新 %2$s 17 | “%1$s”正在扩展 %2$s 18 | “%1$s”正在克隆 %2$s 19 | 20 | “%1$s”已安装新应用 %2$s 21 | “%1$s”已更新 %2$s 22 | \"%1$s\"已更新自己 23 | \"%1$s\"已扩展 %2$s 24 | \"%1$s\"已克隆 %2$s 25 | 已获取的权限:%s 26 | 版本: %s 27 | 版本: %1$s -> %2$s 28 | 适配 Android 版本: %s 29 | 适配 Android: %1$s -> %2$s 30 | 10 (非隔离存储) 31 | 应用设置 32 | 33 | 在哪一个应用中显示关于 %1$s (%2$s) 的信息 34 | 点击“强制停止”可打开其它工具中针对此应用的设置 35 | 尝试传统安装方式 36 | 此应用含有附加组件,可能无法以这种方式正常克隆。如果遇到故障,请尝试重新从应用市场安装。 37 | 38 | 可能是“MIUI 优化”导致(可在“系统设置 - 开发者选项”中关闭) 39 | -------------------------------------------------------------------------------- /mobile/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /mobile/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | compileSdkVersion this.compileSdkVersion 6 | 7 | defaultConfig { 8 | minSdkVersion this.minSdkVersion 9 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 10 | } 11 | 12 | buildFeatures.dataBinding true 13 | 14 | kotlinOptions.jvmTarget = "1.8" 15 | 16 | compileOptions { 17 | sourceCompatibility JavaVersion.VERSION_1_8 18 | targetCompatibility JavaVersion.VERSION_1_8 19 | } 20 | 21 | lintOptions { 22 | check 'NewApi' 23 | abortOnError true 24 | xmlReport false 25 | textReport true 26 | textOutput "stdout" 27 | } 28 | } 29 | 30 | dependencies { 31 | implementation fileTree(dir: 'libs', include: ['*.aar']) 32 | implementation project(':shared') 33 | implementation project(':deagle') 34 | 35 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" 36 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$kotlin_coroutine_version" 37 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutine_version" 38 | implementation 'androidx.core:core-ktx:1.3.2' 39 | implementation "androidx.fragment:fragment-ktx:1.2.5" 40 | implementation 'androidx.lifecycle:lifecycle-common-java8:2.2.0' 41 | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' 42 | implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' 43 | implementation 'androidx.recyclerview:recyclerview:1.1.0' 44 | implementation "androidx.biometric:biometric:1.0.1" 45 | implementation 'com.google.android.material:material:1.2.1' 46 | implementation 'androidx.cardview:cardview:1.0.0' 47 | implementation 'com.google.firebase:firebase-appindexing:19.1.0' // FeatureActionActivity 48 | implementation 'eu.chainfire:libsuperuser:1.0.0.201510071325' 49 | implementation 'uk.co.samuelwall:material-tap-target-prompt:2.0.1' 50 | 51 | androidTestImplementation 'org.mockito:mockito-android:2.8.9' 52 | androidTestImplementation 'androidx.test:runner:1.3.0' 53 | } 54 | -------------------------------------------------------------------------------- /mobile/libs/setup-wizard-lib-platform-release.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/libs/setup-wizard-lib-platform-release.aar -------------------------------------------------------------------------------- /mobile/src/main/java/com/oasisfeng/common/app/BaseAndroidViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.common.app 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.AndroidViewModel 5 | 6 | abstract class BaseAndroidViewModel(app: Application): AndroidViewModel(app) { 7 | 8 | abstract val tag: String 9 | } -------------------------------------------------------------------------------- /mobile/src/main/java/com/oasisfeng/common/app/BaseAppViewModel.java: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.common.app; 2 | 3 | import android.graphics.drawable.Drawable; 4 | import android.text.TextUtils; 5 | import android.view.View; 6 | 7 | import androidx.databinding.ObservableField; 8 | import androidx.lifecycle.ViewModel; 9 | 10 | import com.oasisfeng.android.ui.IconResizer; 11 | import com.oasisfeng.androidx.lifecycle.NonNullMutableLiveData; 12 | import com.oasisfeng.island.IslandApplication; 13 | import com.oasisfeng.island.mobile.R; 14 | 15 | import static android.content.pm.ApplicationInfo.FLAG_SYSTEM; 16 | 17 | /** 18 | * View-model of basic app entry 19 | * 20 | * Created by Oasis on 2016/8/11. 21 | */ 22 | public class BaseAppViewModel extends ViewModel { 23 | 24 | public final AppInfo info; 25 | public final ObservableField icon = new ObservableField<>(); // Issue in data-binding - MutableLiveData causes initially empty icons. 26 | public transient final NonNullMutableLiveData selected = new NonNullMutableLiveData<>(false); 27 | private volatile boolean mIconLoadingStarted; 28 | 29 | public boolean isSystem() { return (info.flags & FLAG_SYSTEM) != 0; } 30 | 31 | @SuppressWarnings("unused") // Used by data binding 32 | public void onViewAttached(final View v) { 33 | if (mIconLoadingStarted) return; 34 | mIconLoadingStarted = true; 35 | info.loadUnbadgedIcon(sIconResizer::createIconThumbnail, icon::set); 36 | } 37 | 38 | public BaseAppViewModel(final AppInfo info) { this.info = info; } 39 | 40 | /* Helper functions for implementing ObservableSortedList.Sortable */ 41 | 42 | protected boolean isSameAs(final BaseAppViewModel another) { 43 | return this == another || info.packageName.equals(another.info.packageName); 44 | } 45 | 46 | protected boolean isContentSameAs(final BaseAppViewModel another) { 47 | return TextUtils.equals(info.getLabel(), another.info.getLabel()) && info.flags == another.info.flags; 48 | } 49 | 50 | private final static IconResizer sIconResizer = new IconResizer((int) IslandApplication.$().getResources().getDimension(R.dimen.app_icon_size)); // TODO: Avoid static 51 | } 52 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/oasisfeng/island/TempDebug.kt: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.island 2 | 3 | import android.app.Activity 4 | 5 | /** 6 | * Stub for temporary debugging code, which is not supposed to be committed to code repository. 7 | * 8 | * Created by Oasis on 2016/8/20. 9 | */ 10 | object TempDebug { 11 | 12 | @JvmStatic fun run(activity: Activity) { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/oasisfeng/island/adb/ProfileRestrictionsSync.java: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.island.adb; 2 | 3 | import android.app.admin.DevicePolicyManager; 4 | import android.content.BroadcastReceiver; 5 | import android.content.ComponentName; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.pm.PackageManager; 9 | import android.os.Bundle; 10 | import android.os.UserManager; 11 | import android.util.Log; 12 | 13 | import com.oasisfeng.island.util.DevicePolicies; 14 | import com.oasisfeng.island.util.Users; 15 | 16 | import static android.os.UserManager.DISALLOW_DEBUGGING_FEATURES; 17 | 18 | /** 19 | * Sync certain user restrictions upon profile starting 20 | * 21 | * Created by Oasis on 2019-5-24. 22 | */ 23 | public class ProfileRestrictionsSync extends BroadcastReceiver { 24 | 25 | @Override public void onReceive(final Context context, final Intent intent) { 26 | final String action = intent.getAction(); 27 | if (! Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action) && ! Intent.ACTION_BOOT_COMPLETED.equals(action)) return; 28 | final DevicePolicies policies = new DevicePolicies(context); 29 | if (Users.isOwner() || ! policies.isProfileOwner()) { // This receiver is not needed in owner user or profile not managed by Island. 30 | context.getPackageManager().setComponentEnabledSetting(new ComponentName(context, getClass()), 31 | PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); 32 | return; 33 | } 34 | final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); 35 | final Bundle owner_restrictions = um.getUserRestrictions(Users.owner); 36 | final Bundle profile_restrictions = policies.invoke(DevicePolicyManager::getUserRestrictions); 37 | final boolean enabled_in_owner = owner_restrictions.getBoolean(DISALLOW_DEBUGGING_FEATURES); 38 | if (profile_restrictions.getBoolean(DISALLOW_DEBUGGING_FEATURES) != enabled_in_owner) { 39 | policies.setUserRestriction(DISALLOW_DEBUGGING_FEATURES, enabled_in_owner); 40 | Log.i(TAG, "Synchronized: " + DISALLOW_DEBUGGING_FEATURES + " = " + enabled_in_owner); 41 | } 42 | } 43 | 44 | private static final String TAG = "Island.PRS"; 45 | } 46 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/oasisfeng/island/data/LiveProfileState.kt: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.island.data 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.content.IntentFilter 7 | import android.os.UserHandle 8 | import android.os.UserManager 9 | import android.util.ArrayMap 10 | import androidx.lifecycle.LiveData 11 | import androidx.lifecycle.Observer 12 | 13 | class LiveProfileStates(private val context: Context) { 14 | 15 | enum class ProfileState { UNAVAILABLE, AVAILABLE, UNLOCKED } 16 | 17 | inner class LiveProfileState(private val profile: UserHandle): LiveData() { 18 | 19 | internal fun updateFromBroadcast(intent: Intent) { 20 | value = when (intent.action) { 21 | Intent.ACTION_MANAGED_PROFILE_AVAILABLE -> ProfileState.AVAILABLE 22 | Intent.ACTION_MANAGED_PROFILE_UNLOCKED -> ProfileState.UNLOCKED 23 | Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE -> ProfileState.UNAVAILABLE 24 | else -> throw IllegalStateException("Unexpected broadcast: $intent") } 25 | } 26 | 27 | override fun onActive() { 28 | mLiveBroadcastReceiver.observeForever(mDummyObserver) 29 | value = context.getSystemService(UserManager::class.java)!!.run { 30 | try { when { 31 | isUserUnlocked(profile) -> ProfileState.UNLOCKED 32 | isUserRunning(profile) -> ProfileState.AVAILABLE 33 | else -> ProfileState.UNAVAILABLE 34 | }} catch (e: SecurityException) { ProfileState.UNAVAILABLE }} // "SecurityException: You need INTERACT_ACROSS_USERS or MANAGE_USERS permission to ...", probably due to Island being destroyed. 35 | } 36 | 37 | override fun onInactive() = mLiveBroadcastReceiver.removeObserver(mDummyObserver) 38 | 39 | private val mDummyObserver = Observer {} 40 | } 41 | 42 | fun get(profile: UserHandle): LiveProfileState = states.getOrPut(profile) { LiveProfileState(profile) } 43 | 44 | val states = ArrayMap() 45 | 46 | val mLiveBroadcastReceiver = object: LiveData() { 47 | 48 | val ACTIONS = arrayOf(Intent.ACTION_MANAGED_PROFILE_AVAILABLE, Intent.ACTION_MANAGED_PROFILE_UNLOCKED, Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE) 49 | 50 | override fun onActive() { context.registerReceiver(mReceiver, IntentFilter().apply { ACTIONS.forEach { addAction(it) }}) } 51 | override fun onInactive() = context.unregisterReceiver(mReceiver) 52 | 53 | private val mReceiver = object: BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { 54 | states[intent.getParcelableExtra(Intent.EXTRA_USER) ?: return]?.updateFromBroadcast(intent) }} 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/oasisfeng/island/data/helper/AppStateTrackingHelper.java: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.island.data.helper; 2 | 3 | import android.app.Activity; 4 | import android.app.Fragment; 5 | import android.os.Bundle; 6 | import android.os.UserHandle; 7 | 8 | import com.oasisfeng.island.data.IslandAppListProvider; 9 | import com.oasisfeng.island.util.Users; 10 | 11 | /** 12 | * Helper for tracking the state of apps. 13 | * 14 | * Created by Oasis on 2019-1-23. 15 | */ 16 | public class AppStateTrackingHelper { 17 | 18 | /** Force refresh the state of specified app when activity is resumed */ 19 | public static void requestSyncWhenResumed(final Activity activity, final String pkg, final UserHandle user) { 20 | activity.getFragmentManager().beginTransaction().add(AppStateSyncFragment.create(pkg, user), pkg + "@" + Users.toId(user)).commit(); 21 | } 22 | 23 | @SuppressWarnings("deprecation") public static class AppStateSyncFragment extends Fragment { 24 | 25 | private static final String KEY_PACKAGE = "package", KEY_USER = "user"; 26 | 27 | @Override public void onResume() { 28 | super.onResume(); 29 | final Activity activity = getActivity(); final Bundle args = getArguments(); 30 | final String pkg = args.getString(KEY_PACKAGE); final UserHandle user = args.getParcelable(KEY_USER); 31 | if (pkg != null && user != null) 32 | IslandAppListProvider.getInstance(activity).refreshPackage(pkg, user, false); 33 | activity.getFragmentManager().beginTransaction().remove(this).commitAllowingStateLoss(); 34 | } 35 | 36 | private static AppStateSyncFragment create(final String pkg, final UserHandle user) { 37 | final AppStateSyncFragment fragment = new AppStateSyncFragment(); 38 | final Bundle args = new Bundle(); 39 | args.putString(KEY_PACKAGE, pkg); 40 | args.putParcelable(KEY_USER, user); 41 | fragment.setArguments(args); 42 | return fragment; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/oasisfeng/island/featured/FeaturedViewModel.java: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.island.featured; 2 | 3 | import android.app.Application; 4 | import android.graphics.drawable.Drawable; 5 | 6 | import com.oasisfeng.android.databinding.ObservableSortedList; 7 | import com.oasisfeng.androidx.lifecycle.NonNullMutableLiveData; 8 | 9 | import java.util.Objects; 10 | import java.util.function.Consumer; 11 | 12 | import androidx.annotation.NonNull; 13 | import androidx.annotation.Nullable; 14 | import androidx.lifecycle.AndroidViewModel; 15 | import androidx.lifecycle.LiveData; 16 | 17 | /** 18 | * Created by Oasis on 2018/5/18. 19 | */ 20 | public class FeaturedViewModel extends AndroidViewModel implements ObservableSortedList.Sortable { 21 | 22 | public final String tag; 23 | public final String title; 24 | public final CharSequence description; 25 | public final @Nullable Drawable icon; 26 | public final LiveData button; 27 | public final @Nullable Consumer function; 28 | public final NonNullMutableLiveData dismissed; 29 | 30 | @Override public boolean isSameAs(final FeaturedViewModel another) { 31 | return tag.equals(another.tag); 32 | } 33 | 34 | @Override public boolean isContentSameAs(final FeaturedViewModel o) { 35 | return Objects.equals(title, o.title) && Objects.equals(description, o.description) && Objects.equals(button, o.button); 36 | } 37 | 38 | @Override public int compareTo(@NonNull final FeaturedViewModel o) { 39 | int result = dismissed.getValue().compareTo(o.dismissed.getValue()); 40 | if (result == 0) result = Integer.compare(order, o.order); 41 | return result; 42 | } 43 | 44 | FeaturedViewModel(final Application app, final int order, final String tag, final String title, final CharSequence description, 45 | final @Nullable Drawable icon, final LiveData button, 46 | final @Nullable Consumer function, final boolean dismissed) { 47 | super(app); 48 | this.order = order; 49 | this.tag = tag; 50 | this.title = title; 51 | this.description = description; 52 | this.icon = icon; 53 | this.button = button; 54 | this.function = function; 55 | this.dismissed = new NonNullMutableLiveData<>(dismissed); 56 | } 57 | 58 | private final int order; 59 | } 60 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/oasisfeng/island/model/IslandViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.island.model 2 | 3 | import android.content.Context 4 | import android.util.Log 5 | import android.widget.Toast 6 | import androidx.lifecycle.viewModelScope 7 | import com.oasisfeng.common.app.BaseAndroidViewModel 8 | import com.oasisfeng.island.analytics.Analytics 9 | import kotlinx.coroutines.CoroutineExceptionHandler 10 | import kotlinx.coroutines.CoroutineScope 11 | import kotlinx.coroutines.future.future 12 | import kotlinx.coroutines.launch 13 | import java.util.concurrent.CancellationException 14 | import java.util.concurrent.CompletableFuture 15 | 16 | fun BaseAndroidViewModel.interactive(context: Context, block: suspend CoroutineScope.() -> Unit) { 17 | viewModelScope.launch(CoroutineExceptionHandler { _, e -> handleException(context, tag, e) }, block = block) 18 | } 19 | 20 | fun BaseAndroidViewModel.interactiveFuture(context: Context, block: suspend CoroutineScope.() -> R): CompletableFuture { 21 | return viewModelScope.future(block = block).exceptionally { t -> null.also { handleException(context, tag, t) }} 22 | } 23 | 24 | private fun handleException(context: Context, tag: String, t: Throwable) { 25 | if (t is CancellationException) return Unit.also { Log.i(tag, "Interaction canceled: ${t.message}") } 26 | Analytics().logAndReport(tag, "Unexpected internal error", t) 27 | Toast.makeText(context, "Internal error: " + t.message, Toast.LENGTH_LONG).show() 28 | } 29 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/oasisfeng/island/model/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.island.model 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import android.os.Build.VERSION.SDK_INT 6 | import android.os.Build.VERSION_CODES.Q 7 | import android.os.UserHandle 8 | import android.util.Log 9 | import androidx.fragment.app.FragmentActivity 10 | import androidx.lifecycle.SavedStateHandle 11 | import com.google.android.material.tabs.TabLayout 12 | import com.oasisfeng.island.analytics.analytics 13 | import com.oasisfeng.island.data.LiveProfileStates 14 | import com.oasisfeng.island.data.LiveProfileStates.ProfileState 15 | import com.oasisfeng.island.mobile.R 16 | import com.oasisfeng.island.settings.IslandNameManager 17 | import com.oasisfeng.island.util.Users 18 | import com.oasisfeng.island.util.toId 19 | 20 | class MainViewModel(app: Application, state: SavedStateHandle): AppListViewModel(app, state) { 21 | 22 | private val mProfileStates = LiveProfileStates(app) 23 | 24 | fun initializeTabs(activity: FragmentActivity, tabs: TabLayout) { 25 | tabs.tabIconTint = null 26 | tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { 27 | override fun onTabSelected(tab: TabLayout.Tab) = onTabSwitched(activity, tabs, tab) 28 | override fun onTabUnselected(tab: TabLayout.Tab) {} 29 | override fun onTabReselected(tab: TabLayout.Tab) {}}) 30 | 31 | // Tab "Discovery" and "Mainland" are always present 32 | tabs.addTab(tabs.newTab().setText(R.string.tab_discovery),/* selected = */false) 33 | val currentProfile = currentProfile 34 | tabs.addTab(tabs.newTab().setText(R.string.tab_mainland),/* selected = */Users.owner == currentProfile) 35 | 36 | for ((profile, name) in IslandNameManager.getAllNames(activity)) { 37 | val tab = tabs.newTab().setTag(profile).setText(name) 38 | mProfileStates.get(profile).observe(activity, { updateTabIconForProfileState(activity, tab, profile, it) }) 39 | tabs.addTab(tab,/* selected = */profile == currentProfile) } 40 | } 41 | } 42 | 43 | private fun updateTabIconForProfileState(context: Context, tab: TabLayout.Tab, profile: UserHandle, state: ProfileState) { 44 | Log.d(TAG, "Update tab icon for profile ${profile.toId()}: $state") 45 | val icon = context.getDrawable(R.drawable.ic_island_black_24dp)!! 46 | icon.setTint(context.getColor(if (state == ProfileState.UNAVAILABLE) R.color.state_frozen else R.color.state_alive)) 47 | try { tab.icon = context.packageManager.getUserBadgedIcon(icon, profile) } 48 | // (Mostly "vivo" devices before Android Q) "SecurityException: You need MANAGE_USERS permission to: check if specified user a managed profile outside your profile group" 49 | catch (e: SecurityException) { if (SDK_INT >= Q) analytics().logAndReport(TAG, "Error getting user badged icon", e) } 50 | } 51 | 52 | private const val TAG = "Island.MVM" -------------------------------------------------------------------------------- /mobile/src/main/java/com/oasisfeng/island/security/SecurityPrompt.java: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.island.security; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | import android.os.Looper; 6 | import android.widget.Toast; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.annotation.StringRes; 10 | import androidx.biometric.BiometricPrompt; 11 | import androidx.fragment.app.FragmentActivity; 12 | 13 | import com.oasisfeng.island.mobile.R; 14 | 15 | /** 16 | * Security protection based on biometric or lock-screen credentials. 17 | * 18 | * Created by Oasis on 2019-1-17. 19 | */ 20 | public class SecurityPrompt { 21 | 22 | public static void showBiometricPrompt(final FragmentActivity activity, final @StringRes int title, 23 | final @StringRes int description, final Runnable on_authenticated) { 24 | final Handler handler = new Handler(Looper.getMainLooper()); 25 | final Context app_context = activity.getApplication(); 26 | new BiometricPrompt(activity, handler::post, new BiometricPrompt.AuthenticationCallback() { 27 | 28 | @Override public void onAuthenticationSucceeded(@NonNull final BiometricPrompt.AuthenticationResult result) { 29 | on_authenticated.run(); 30 | } 31 | 32 | @Override public void onAuthenticationError(final int error, @NonNull final CharSequence error_message) { 33 | if (error == BiometricPrompt.ERROR_CANCELED | error == BiometricPrompt.ERROR_NEGATIVE_BUTTON) return; 34 | Toast.makeText(app_context, error_message, Toast.LENGTH_LONG).show(); 35 | // if (error == BiometricPrompt.ERROR_TIMEOUT) return; 36 | // 37 | // TODO: Security confirmation with UserManager.createUserCreationIntent() 38 | } 39 | 40 | @Override public void onAuthenticationFailed() { 41 | Toast.makeText(app_context, R.string.toast_security_confirmation_failure, Toast.LENGTH_LONG).show(); 42 | } 43 | }).authenticate(new BiometricPrompt.PromptInfo.Builder().setTitle(activity.getText(title)) 44 | .setDescription(activity.getText(description)).setNegativeButtonText(activity.getText(android.R.string.cancel)).build()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/oasisfeng/island/settings/Preferences.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package com.oasisfeng.island.settings 4 | 5 | import android.os.Build.VERSION.SDK_INT 6 | import android.os.Build.VERSION_CODES.O 7 | import android.preference.* 8 | import androidx.annotation.StringRes 9 | 10 | inline fun PreferenceFragment.setup(@StringRes key: Int, crossinline block: T.() -> Unit) 11 | = @Suppress("UNCHECKED_CAST") (findPreference(getString(key)) as? T)?.apply { block() } 12 | 13 | @Suppress("DEPRECATION") fun PreferenceFragment.remove(preference: Preference) { 14 | if (SDK_INT >= O) preference.parent?.apply { removePreference(preference); if (this is PreferenceCategory && preferenceCount == 0) remove(this) } 15 | else removeLeafPreference(preferenceScreen, preference) 16 | } 17 | 18 | fun removeLeafPreference(root: PreferenceGroup, preference: Preference): Boolean { 19 | if (root.removePreference(preference)) return true 20 | for (i in 0 until root.preferenceCount) { 21 | val child = root.getPreference(i) 22 | if (child is PreferenceGroup && removeLeafPreference(child, preference)) { 23 | if (child is PreferenceCategory && child.preferenceCount == 0) root.removePreference(child) 24 | return true 25 | } 26 | } 27 | return false 28 | } 29 | 30 | fun Preference.onClick(block: Preference.() -> Unit) { onPreferenceClickListener = Preference.OnPreferenceClickListener { block(); true }} 31 | inline fun TwoStatePreference.onChange(crossinline block: (Boolean) -> Boolean) = setOnPreferenceChangeListener { _, v -> block(v as Boolean) } 32 | inline fun EditTextPreference.onChange(crossinline block: (String) -> Boolean) = setOnPreferenceChangeListener { _, v -> block(v as String) } 33 | fun TwoStatePreference.lock(checked: Boolean) { isChecked = checked; isSelectable = false } 34 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/oasisfeng/island/setup/SetupActivity.java: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.island.setup; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | 6 | import com.android.setupwizardlib.util.SystemBarHelper; 7 | import com.oasisfeng.island.mobile.R; 8 | 9 | /** 10 | * Island setup wizard 11 | * 12 | * Created by Oasis on 2016/9/13. 13 | */ 14 | public class SetupActivity extends Activity { 15 | 16 | @Override protected void onCreate(final Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | // getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 19 | SystemBarHelper.hideSystemBars(getWindow()); 20 | setContentView(R.layout.activity_main); 21 | if (savedInstanceState == null) 22 | getFragmentManager().beginTransaction().replace(R.id.container, new SetupWizardFragment()).commit(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/oasisfeng/island/util/FloatingActionButtonBehavior.java: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.island.util; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import com.google.android.material.bottomsheet.BottomSheetBehavior; 9 | import com.google.android.material.floatingactionbutton.FloatingActionButton; 10 | import com.google.android.material.snackbar.Snackbar; 11 | 12 | import androidx.coordinatorlayout.widget.CoordinatorLayout; 13 | 14 | /** 15 | * Expand the default behavior of FloatingActionButton to support {@link BottomSheetBehavior} 16 | * 17 | * Created by Oasis on 2016/6/24. 18 | */ 19 | public class FloatingActionButtonBehavior extends FloatingActionButton.Behavior { 20 | 21 | @Override public boolean layoutDependsOn(final CoordinatorLayout parent, final FloatingActionButton child, final View dependency) { 22 | return super.layoutDependsOn(parent, child, dependency) || isBottomSheet(dependency); 23 | } 24 | 25 | @Override public boolean onDependentViewChanged(final CoordinatorLayout parent, final FloatingActionButton child, final View dependency) { 26 | // Block parent behavior for SnackBar if bottom sheet is visible 27 | if (dependency instanceof Snackbar.SnackbarLayout) { 28 | final ViewGroup.LayoutParams fab_general_params = child.getLayoutParams(); 29 | if (fab_general_params instanceof CoordinatorLayout.LayoutParams) { 30 | final CoordinatorLayout.LayoutParams fab_params = ((CoordinatorLayout.LayoutParams) fab_general_params); 31 | final int anchor_id = fab_params.getAnchorId(); 32 | if (anchor_id != 0) { 33 | final View anchor = parent.findViewById(anchor_id); 34 | if (anchor != null && anchor.getVisibility() == View.VISIBLE) return false; 35 | } 36 | } 37 | } 38 | return super.onDependentViewChanged(parent, child, dependency); 39 | } 40 | 41 | private boolean isBottomSheet(final View view) { 42 | final ViewGroup.LayoutParams params = view.getLayoutParams(); 43 | return params instanceof CoordinatorLayout.LayoutParams 44 | && ((CoordinatorLayout.LayoutParams) params).getBehavior() instanceof BottomSheetBehavior; 45 | } 46 | 47 | public FloatingActionButtonBehavior() {} 48 | public FloatingActionButtonBehavior(final Context context, final AttributeSet attrs) {} 49 | } 50 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/oasisfeng/island/util/SimpleAsyncTask.java: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.island.util; 2 | 3 | import android.os.AsyncTask; 4 | import androidx.annotation.WorkerThread; 5 | 6 | /** 7 | * Simplified {@link AsyncTask} without parameters and result. 8 | * 9 | * Created by Oasis on 2016/11/6. 10 | */ 11 | public abstract class SimpleAsyncTask extends AsyncTask { 12 | 13 | public static void execute(final Runnable do_in_background, final Runnable on_post_execute) { 14 | new SimpleAsyncTask() { 15 | @Override protected void doInBackground() { 16 | do_in_background.run(); 17 | } 18 | 19 | @Override protected void onPostExecute() { 20 | on_post_execute.run(); 21 | } 22 | }.execute(); 23 | } 24 | 25 | protected abstract void doInBackground(); 26 | protected abstract void onPostExecute(); 27 | 28 | @Override @WorkerThread protected final Void doInBackground(final Void... params) { doInBackground(); return null; } 29 | @Override protected final void onPostExecute(final Void aVoid) { onPostExecute(); } 30 | } 31 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/oasisfeng/island/util/TextFormat.java: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.island.util; 2 | 3 | import android.content.Context; 4 | import android.text.Html; 5 | import android.text.SpannedString; 6 | 7 | import androidx.annotation.Nullable; 8 | import androidx.annotation.StringRes; 9 | 10 | /** 11 | * Utility class for text format. 12 | * 13 | * Created by Oasis on 2017/8/28. 14 | */ 15 | public class TextFormat { 16 | 17 | public static @Nullable CharSequence getText(final Context context, final @StringRes int text, final Object... args) { 18 | return text == 0 ? null : args == null || args.length == 0 ? context.getText(text) 19 | : Html.fromHtml(String.format(Html.toHtml(new SpannedString(context.getText(text))), args)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/oasisfeng/island/widget/PersistableSearchView.java: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.island.widget; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.widget.SearchView; 6 | 7 | import androidx.annotation.Nullable; 8 | 9 | /** 10 | * Do not reset query text upon expanding and collapsing. 11 | * 12 | * Created by Oasis on 2019-6-30. 13 | */ 14 | public class PersistableSearchView extends SearchView { 15 | 16 | @Override public void onActionViewExpanded() { 17 | super.setOnQueryTextListener(null); // onActionViewExpanded() does not call setQuery() 18 | super.onActionViewExpanded(); 19 | super.setOnQueryTextListener(mOnQueryTextListener); 20 | } 21 | 22 | @Override public void onActionViewCollapsed() { 23 | mBlockSetQuery = true; 24 | super.onActionViewCollapsed(); 25 | mBlockSetQuery = false; 26 | if (mOnCloseListener != null) mOnCloseListener.onClose(); 27 | } 28 | 29 | @Override public void setQuery(final CharSequence query, final boolean submit) { 30 | if (mBlockSetQuery) mBlockSetQuery = false; 31 | else super.setQuery(query, submit); 32 | } 33 | 34 | @Override public void setOnCloseListener(final OnCloseListener listener) { super.setOnCloseListener(mOnCloseListener = listener); } 35 | @Override public void setOnQueryTextListener(final OnQueryTextListener listener) { super.setOnQueryTextListener(mOnQueryTextListener = listener); } 36 | 37 | public PersistableSearchView(final Context context) { super(context); } 38 | public PersistableSearchView(final Context context, final AttributeSet attrs) { super(context, attrs); } 39 | public PersistableSearchView(final Context context, final AttributeSet attrs, final int defStyleAttr) { super(context, attrs, defStyleAttr); } 40 | @SuppressWarnings("unused") public PersistableSearchView(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) { 41 | super(context, attrs, defStyleAttr, defStyleRes); 42 | } 43 | 44 | private boolean mBlockSetQuery; 45 | private @Nullable OnCloseListener mOnCloseListener; 46 | private @Nullable OnQueryTextListener mOnQueryTextListener; 47 | } 48 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/oasisfeng/settings/ActionButtonPreference.java: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.settings; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | import android.widget.ImageView; 7 | 8 | import com.oasisfeng.island.mobile.R; 9 | 10 | import androidx.annotation.DrawableRes; 11 | import androidx.annotation.StringRes; 12 | 13 | /** A preference with a clickable action icon on the side */ 14 | public class ActionButtonPreference extends AdvancedPreference implements View.OnClickListener { 15 | 16 | public void setSummaryAndActionButton(final @StringRes int summary, final @DrawableRes int icon, final OnPreferenceClickListener listener) { 17 | setSummaryAndNotSelectable(summary); 18 | mActionIcon = icon; 19 | mOnActionClickListener = listener; 20 | notifyChanged(); 21 | } 22 | 23 | @Override protected void onBindView(final View view) { 24 | super.onBindView(view); 25 | final ImageView icon = (ImageView) view.findViewById(R.id.preference_action_button); 26 | if (mActionIcon != 0) { 27 | icon.setImageDrawable(getContext().getDrawable(mActionIcon)); 28 | icon.setOnClickListener(this); 29 | icon.setEnabled(true); // Make icon available even if the preference itself is disabled. 30 | icon.setVisibility(View.VISIBLE); 31 | } else icon.setVisibility(View.GONE); 32 | } 33 | 34 | @Override public void onClick(final View v) { 35 | if (v.getId() == R.id.preference_action_button) 36 | if (mOnActionClickListener != null) 37 | mOnActionClickListener.onPreferenceClick(this); 38 | } 39 | 40 | public ActionButtonPreference(final Context context, final AttributeSet attrs) { 41 | super(context, attrs); 42 | mActionIcon = attrs.getAttributeResourceValue("http://schemas.android.com/apk/res-auto", "icon", 0); 43 | setWidgetLayoutResource(R.layout.preference_widget_action_button); 44 | } 45 | 46 | private @DrawableRes int mActionIcon; 47 | private OnPreferenceClickListener mOnActionClickListener; 48 | } 49 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/oasisfeng/settings/AdvancedPreference.java: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.settings; 2 | 3 | import android.content.ComponentName; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.preference.Preference; 7 | import android.util.AttributeSet; 8 | 9 | import androidx.annotation.StringRes; 10 | 11 | /** 12 | * Improved {@link Preference} 13 | * 14 | * Created by Oasis on 2017/3/7. 15 | */ 16 | public class AdvancedPreference extends Preference { 17 | 18 | private static final String PACKAGE_PLACEHOLDER_SELF = "self"; 19 | 20 | @Override public void setIntent(final Intent intent) { 21 | final ComponentName component = intent.getComponent(); 22 | if (component != null && PACKAGE_PLACEHOLDER_SELF.equals(component.getPackageName())) { 23 | if (component.getClassName().isEmpty()) intent.setPackage(getContext().getPackageName()).setComponent(null); 24 | else intent.setComponent(new ComponentName(getContext().getPackageName(), component.getClassName())); 25 | } 26 | super.setIntent(intent); 27 | } 28 | 29 | public void setSummaryAndNotSelectable(final @StringRes int summary) { 30 | setSummary(summary); 31 | setSelectable(false); 32 | } 33 | 34 | public void setSummaryAndClickListener(final @StringRes int summary, final OnPreferenceClickListener listener) { 35 | setSummary(summary); 36 | setSelectable(true); 37 | setOnPreferenceClickListener(listener); 38 | } 39 | 40 | public AdvancedPreference(final Context context, final AttributeSet attrs) { 41 | super(context, attrs); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /mobile/src/main/java/com/oasisfeng/ui/card/CardViewModel.java: -------------------------------------------------------------------------------- 1 | package com.oasisfeng.ui.card; 2 | 3 | import android.content.Context; 4 | import android.view.View; 5 | 6 | import com.oasisfeng.island.mobile.R; 7 | 8 | import androidx.annotation.ColorInt; 9 | import androidx.annotation.StringRes; 10 | import androidx.cardview.widget.CardView; 11 | import androidx.core.content.ContextCompat; 12 | 13 | /** 14 | * View-model for card 15 | * 16 | * Created by Oasis on 2017/9/7. 17 | */ 18 | public class CardViewModel { 19 | 20 | public CharSequence title; 21 | public CharSequence text; 22 | public CharSequence button_start; 23 | public CharSequence button_end; 24 | public @ColorInt int color; 25 | public boolean dismissible = true; 26 | 27 | protected CardViewModel(final Context context, final @StringRes int title, final @StringRes int text, 28 | final @StringRes int button_start, final @StringRes int button_end) { 29 | this.title = title == 0 ? null : context.getText(title); 30 | this.text = text == 0 ? null : context.getText(text); 31 | this.button_start = button_start == 0 ? null : context.getText(button_start); 32 | this.button_end = button_end == 0 ? null : context.getText(button_end); 33 | color = ContextCompat.getColor(context, R.color.card_attention); 34 | } 35 | 36 | @SuppressWarnings("MethodMayBeStatic") protected void dismiss(final CardView card) { 37 | card.setVisibility(View.GONE); 38 | } 39 | 40 | public void onButtonStartClick(final Context context, final CardView card) {} 41 | public void onButtonEndClick(final Context context, final CardView card) {} 42 | } 43 | -------------------------------------------------------------------------------- /mobile/src/main/res/animator/slide_back_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /mobile/src/main/res/animator/slide_back_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /mobile/src/main/res/animator/slide_next_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /mobile/src/main/res/animator/slide_next_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /mobile/src/main/res/color/chip_background_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-nodpi/ic_launcher_appops.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/drawable-nodpi/ic_launcher_appops.webp -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-nodpi/ic_launcher_fx.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/drawable-nodpi/ic_launcher_fx.webp -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-nodpi/ic_launcher_greenify.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/drawable-nodpi/ic_launcher_greenify.webp -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-nodpi/ic_launcher_saf_enhancer.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/drawable-nodpi/ic_launcher_saf_enhancer.webp -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxxhdpi/setup_header.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/drawable-xxxhdpi/setup_header.9.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/app_icon_empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/bottom_navigation_text_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_add_to_photos_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_explore_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_filter_list_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_info_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_island_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_launch_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_lightbulb_outline_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_lock_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_mainland_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_search_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_settings_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /mobile/src/main/res/drawable/ic_unlock_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /mobile/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /mobile/src/main/res/layout/preference_widget_action_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /mobile/src/main/res/layout/setup_wizard.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 23 | 30 | 31 | 32 | 39 | 40 | 45 | 46 | 53 | 54 |