├── .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* @StringRes */Integer> 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 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/mobile/src/main/res/menu/app_actions.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/mobile/src/main/res/menu/main_actions.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/mobile/src/main/res/menu/pref_island_actions.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-hdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-hdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-mdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-mdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-xhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-xxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oasisfeng/island/d63538212a9f417c180bdb9258c0f5461de53c27/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/mobile/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #5C6BC0
5 | #121212
6 | #121212
7 | #2C2C2C
8 | #2C2C2C
9 | #635732
10 | #259B24
11 |
12 | #DD6F00
13 | #536dfe
14 | @android:color/darker_gray
15 | #536dfe
16 |
17 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | @color/primary_dark
5 | #F3F3F3
6 | @android:color/white
7 | @android:color/white
8 | @android:color/white
9 | #FFFFE082
10 | #259B24
11 |
12 | #FF8F00
13 | @color/primary_dark
14 | @android:color/black
15 | @color/primary_dark
16 |
17 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values/constants.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ❄
4 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 8dp
5 | 16dp
6 | 32dp
7 | 2dp
8 | 8dp
9 |
10 |
11 |
12 | 16dp
13 | 12dp
14 | 8dp
15 |
16 | 36dp
17 | 12dp
18 |
19 |
20 | 234dp
21 | 100dp
22 |
23 |
24 | 24dp
25 | 40dp
26 | 34dp
27 | 16sp
28 | 16dp
29 |
30 |
31 | 240dp
32 | 240dp
33 |
34 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values/preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | launch_shortcut_prefix
5 | show_admin_message
6 | preserve_app_ops
7 |
8 | island_name
9 | device_owner_setup
10 | privacy
11 | appops
12 | manage_read_phone_state
13 | manage_read_sms
14 | manage_location
15 | manage_storage
16 | cross_profile
17 | watcher
18 | island_watcher
19 | app_watcher
20 | setup
21 | reprovision
22 | destroy
23 |
24 | privacy_notification_clone
25 |
26 | version
27 |
--------------------------------------------------------------------------------
/mobile/src/main/res/xml/actions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/mobile/src/main/res/xml/pref_about.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/mobile/src/main/res/xml/pref_general.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
12 |
13 |
14 |
15 |
18 |
19 |
25 |
26 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/mobile/src/main/res/xml/pref_headers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
14 |
15 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/mobile/src/main/res/xml/searchable.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/open/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 |
11 | kotlinOptions.jvmTarget = "1.8"
12 |
13 | compileOptions {
14 | sourceCompatibility JavaVersion.VERSION_1_8
15 | targetCompatibility JavaVersion.VERSION_1_8
16 | }
17 | }
18 |
19 | dependencies {
20 | implementation project(':shared')
21 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
22 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$kotlin_coroutine_version"
23 | }
24 |
--------------------------------------------------------------------------------
/open/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/open/src/main/java/android/app/DerivedAppOpsManager.java:
--------------------------------------------------------------------------------
1 | package android.app;
2 |
3 | /**
4 | * Intermediate class for {@link AppOpsManager} derivation
5 | *
6 | * Created by Oasis on 2019-4-30.
7 | */
8 | public class DerivedAppOpsManager extends AppOpsManager {}
9 |
--------------------------------------------------------------------------------
/open/src/main/java/android/app/admin/DerivedDevicePolicyManager.java:
--------------------------------------------------------------------------------
1 | package android.app.admin;
2 |
3 | /**
4 | * Intermediate class for {@link DevicePolicyManager} derivation
5 | *
6 | * Created by Oasis on 2019-4-28.
7 | */
8 | public class DerivedDevicePolicyManager extends DevicePolicyManager {}
9 |
--------------------------------------------------------------------------------
/open/src/main/java/android/content/DerivedRestrictionsManager.java:
--------------------------------------------------------------------------------
1 | package android.content;
2 |
3 | /**
4 | * Intermediate class for {@link RestrictionsManager} derivation
5 | *
6 | * Created by Oasis on 2019-6-6.
7 | */
8 | public class DerivedRestrictionsManager extends RestrictionsManager {}
9 |
--------------------------------------------------------------------------------
/open/src/main/java/com/oasisfeng/island/ApiConstants.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.admin.DevicePolicyManager;
5 | import android.content.RestrictionsManager;
6 | import android.os.PersistableBundle;
7 | import android.os.UserHandle;
8 |
9 | /**
10 | * API-related constants
11 | *
12 | * Created by Oasis on 2019-6-10.
13 | */
14 | @SuppressLint("InlinedApi") public class ApiConstants {
15 |
16 | /**
17 | * Both as request type in {@link RestrictionsManager#requestPermission(String, String, PersistableBundle)}
18 | * and restriction key in {@link RestrictionsManager#getApplicationRestrictions()} (value: authorized delegations in string array)
19 | */
20 | static final String TYPE_DELEGATION = "com.oasisfeng.island.delegation"; // Name-spaced as requirement mentioned by RestrictionsManager.requestPermission()
21 |
22 | /**
23 | * Optional user serial number to request cross-user authorization. (Without this key, default to current user only)
24 | *
25 | * @see android.os.UserManager#getSerialNumberForUser(UserHandle)
26 | */
27 | public static final String REQUEST_KEY_USER_SERIAL_NUMBER = "com.oasisfeng.island.request.user";
28 |
29 | /** Special value for {@link #REQUEST_KEY_USER_SERIAL_NUMBER}, to apply for all users. */
30 | public static final long USER_ALL = -1;
31 |
32 | public static final String DELEGATION_PACKAGE_ACCESS = DevicePolicyManager.DELEGATION_PACKAGE_ACCESS;
33 | public static final String DELEGATION_PERMISSION_GRANT = DevicePolicyManager.DELEGATION_PERMISSION_GRANT;
34 | /* Custom delegations with prefix "-island-" */
35 | public static final String DELEGATION_APP_OPS = "-island-delegation-app-ops";
36 | }
37 |
--------------------------------------------------------------------------------
/open/src/main/java/com/oasisfeng/island/SystemServiceBridge.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island;
2 |
3 | import android.app.Service;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.pm.ResolveInfo;
7 | import android.os.IBinder;
8 | import android.os.UserHandle;
9 | import android.util.Log;
10 |
11 | import androidx.annotation.Nullable;
12 |
13 | import com.oasisfeng.island.api.DelegatedAppOpsManager;
14 | import com.oasisfeng.island.api.DelegatedDevicePolicyManager;
15 | import com.oasisfeng.island.api.PermissionForwardingRestrictionsManager;
16 | import com.oasisfeng.island.util.Users;
17 |
18 | import java.util.Objects;
19 |
20 | /**
21 | * Created by Oasis on 2019-6-5.
22 | */
23 | public class SystemServiceBridge extends Service {
24 |
25 | @Nullable @Override public IBinder onBind(final Intent intent) {
26 | Log.v(TAG, "onBind: " + intent);
27 | // This check essentially verifies action and scheme without hardcoded string.
28 | final ResolveInfo service = getPackageManager().resolveService(new Intent(intent.getAction(), intent.getData()).setPackage(getPackageName()), 0);
29 | if (service == null || ! getClass().getName().equals(service.serviceInfo.name)) return null;
30 |
31 | final String ssp = Objects.requireNonNull(intent.getData()).getSchemeSpecificPart();
32 | final UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
33 | if (ssp != null) switch (ssp) {
34 | case Context.RESTRICTIONS_SERVICE:
35 | try {
36 | return PermissionForwardingRestrictionsManager.buildBinderProxy(this, user);
37 | } catch (final ReflectiveOperationException e) {
38 | Log.e(TAG, "Error preparing delegated RestrictionsManager", e);
39 | } catch (final PermissionForwardingRestrictionsManager.IncompatibilityException e) {
40 | Log.e(TAG, "Incompatible ROM");
41 | }
42 | break;
43 | case Context.DEVICE_POLICY_SERVICE:
44 | try {
45 | return DelegatedDevicePolicyManager.buildBinderProxy(this);
46 | } catch (final ReflectiveOperationException e) {
47 | Log.e(TAG, "Error preparing delegated DevicePolicyManager", e);
48 | break;
49 | }
50 | case Context.APP_OPS_SERVICE:
51 | try {
52 | return DelegatedAppOpsManager.buildBinderProxy(this);
53 | } catch (final ReflectiveOperationException e) {
54 | Log.e(TAG, "Error preparing delegated AppOpsManager", e);
55 | break;
56 | }
57 |
58 | default: Log.w(TAG, "Unsupported system service: " + ssp);
59 | }
60 | return null;
61 | }
62 |
63 | @Override public void onCreate() {
64 | Users.refreshUsers(this); // Not initialized automatically due to separate process
65 | }
66 |
67 | private static final String TAG = "Island.SSB";
68 | }
69 |
--------------------------------------------------------------------------------
/open/src/main/java/com/oasisfeng/island/api/DelegatedDevicePolicyManager.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.api;
2 |
3 | import android.app.admin.DerivedDevicePolicyManager;
4 | import android.app.admin.DevicePolicyManager;
5 | import android.content.Context;
6 | import android.os.Binder;
7 |
8 | import com.oasisfeng.island.ApiConstants;
9 | import com.oasisfeng.island.RestrictedBinderProxy;
10 |
11 | import static android.content.Context.DEVICE_POLICY_SERVICE;
12 |
13 | /**
14 | * Delegated {@link DevicePolicyManager}
15 | *
16 | * Created by Oasis on 2019-4-30.
17 | */
18 | public class DelegatedDevicePolicyManager extends DerivedDevicePolicyManager {
19 |
20 | public static Binder buildBinderProxy(final Context context) throws ReflectiveOperationException {
21 | return new DelegatedDevicePolicyManager(context).mBinderProxy;
22 | }
23 |
24 | private DelegatedDevicePolicyManager(final Context c) throws ReflectiveOperationException {
25 | mBinderProxy = sHelper.inject(this, c, DEVICE_POLICY_SERVICE, ApiConstants.DELEGATION_PACKAGE_ACCESS);
26 |
27 | // Whiltelist supported APIs by invoking them (with dummy arguments) before seal().
28 | setApplicationHidden(null, null, false);
29 | isApplicationHidden(null, null);
30 | mBinderProxy.seal();
31 | }
32 |
33 | private final RestrictedBinderProxy mBinderProxy;
34 |
35 | private static final DerivedManagerHelper sHelper = new DerivedManagerHelper<>(DevicePolicyManager.class);
36 | }
37 |
--------------------------------------------------------------------------------
/open/src/main/res/values-pt-rBR/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Pedido de autorização de %s
4 | Desbloquear as notificações para mostrar o pedido de autorização
5 | Permitir para %s, sempre?
6 | Autorizar
7 | Suspender e ativar os apps
8 | Controlar a permissão do tempo de execução para os apps (incluir suspensão)
9 | Gerenciar App Ops (restrições de permissão)
10 |
11 |
--------------------------------------------------------------------------------
/open/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 授權請求 來自 %s
4 | 請恢復通知以顯示授權請求
5 | 允許其(在任何时候)%s
6 | 同意
7 | 凍結及解凍應用程式
8 | 控制應用程式的執行期權限(包括 鎖定)
9 | 管理應用程式的權限限制 (App Ops)
10 |
--------------------------------------------------------------------------------
/open/src/main/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 授权请求 来自 %s
4 | 请恢复通知以显示授权请求
5 | 允许其(在任何时候)%s
6 | 同意
7 | 冻结及解冻应用
8 | 控制应用的运行期权限(包括 锁定)
9 | 管理应用的权限限制 (App Ops)
10 |
--------------------------------------------------------------------------------
/open/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Authorization request from %s
4 | Unblock notifications to show authorization request
5 | Allow it to %s, at any time?
6 | Authorize
7 | freeze and unfreeze apps
8 | control runtime permission for apps (include locking)
9 | manage app ops (permission restrictions)
10 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include 'shared', 'engine', 'mobile', 'fileprovider', 'installer', 'watcher', 'open'
2 | include 'assembly'
3 | include ':deagle'; project(':deagle').projectDir = new File(settingsDir, '../deagle/library')
4 |
--------------------------------------------------------------------------------
/shared/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdkVersion 30
6 |
7 | defaultConfig {
8 | minSdkVersion this.minSdkVersion
9 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
10 | }
11 |
12 | buildTypes {
13 | release { buildConfigField("boolean", "CRASHLYTICS_ENABLED", "true") }
14 | debug { buildConfigField("boolean", "CRASHLYTICS_ENABLED", "false") }
15 | }
16 |
17 | kotlinOptions.jvmTarget = "1.8"
18 |
19 | compileOptions {
20 | sourceCompatibility JavaVersion.VERSION_1_8
21 | targetCompatibility JavaVersion.VERSION_1_8
22 | }
23 |
24 | lintOptions {
25 | check 'NewApi'
26 | abortOnError true
27 | xmlReport false
28 | textReport true
29 | textOutput "stdout"
30 | }
31 |
32 | // Enable lint checking in all build variants.
33 | libraryVariants.all { variant ->
34 | variant.outputs.each { output ->
35 | def lintTask = tasks["lint${variant.name.capitalize()}"]
36 | output.assemble.dependsOn lintTask
37 | }
38 | }
39 | }
40 |
41 | dependencies {
42 | api project(':deagle')
43 | api 'com.google.code.findbugs:jsr305:3.0.2'
44 | implementation 'org.jetbrains:annotations:15.0'
45 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
46 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$kotlin_coroutine_version"
47 | implementation 'androidx.core:core-ktx:1.3.2'
48 | implementation 'com.google.android.gms:play-services-analytics:17.0.0'
49 | implementation 'com.google.firebase:firebase-core:17.5.1'
50 | implementation 'com.google.firebase:firebase-analytics:17.6.0'
51 | implementation 'com.google.firebase:firebase-config:19.2.0'
52 | implementation 'com.google.firebase:firebase-crashlytics:17.2.2'
53 | implementation 'com.oasisfeng.condom:library:2.5.0'
54 | implementation 'com.squareup.okhttp3:okhttp:3.12.1'
55 | implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.10.0'
56 |
57 | testImplementation 'junit:junit:4.12'
58 | testImplementation 'org.mockito:mockito-core:2.18.3'
59 |
60 | androidTestImplementation 'junit:junit:4.12'
61 | androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlin_coroutine_version"
62 | androidTestImplementation 'androidx.test:core:1.3.0'
63 | androidTestImplementation 'androidx.test:runner:1.3.0'
64 | androidTestImplementation 'androidx.test:rules:1.3.0'
65 | }
66 |
--------------------------------------------------------------------------------
/shared/src/main/aidl/com/oasisfeng/island/shuttle/IMethodShuttle.aidl:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.shuttle;
2 |
3 | import com.oasisfeng.island.shuttle.MethodInvocation;
4 |
5 | interface IMethodShuttle {
6 | void invoke(inout MethodInvocation invocation);
7 | }
8 |
--------------------------------------------------------------------------------
/shared/src/main/aidl/com/oasisfeng/island/shuttle/IServiceConnection.aidl:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.shuttle;
2 |
3 | import android.content.ComponentName;
4 | import com.oasisfeng.island.shuttle.IUnbinder;
5 |
6 | interface IServiceConnection {
7 | oneway void onServiceConnected(in ComponentName name, in IBinder service, in IUnbinder unbinder);
8 | oneway void onServiceDisconnected(in ComponentName name);
9 | oneway void onServiceFailed();
10 | }
11 |
--------------------------------------------------------------------------------
/shared/src/main/aidl/com/oasisfeng/island/shuttle/IUnbinder.aidl:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.shuttle;
2 |
3 | interface IUnbinder {
4 | boolean unbind();
5 | }
6 |
--------------------------------------------------------------------------------
/shared/src/main/aidl/com/oasisfeng/island/shuttle/MethodInvocation.aidl:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.shuttle;
2 |
3 | parcelable MethodInvocation;
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/android/content/IntentCompat.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.android.content;
2 |
3 | import androidx.annotation.RequiresApi;
4 |
5 | import static android.os.Build.VERSION_CODES.O;
6 |
7 | /**
8 | * Created by Oasis on 2018-11-27.
9 | */
10 | public class IntentCompat {
11 |
12 | public static final String ACTION_SHOW_APP_INFO = "android.intent.action.SHOW_APP_INFO";
13 | public static final String EXTRA_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME";
14 | @RequiresApi(O) public static final String EXTRA_AUTO_LAUNCH_SINGLE_CHOICE = "android.intent.extra.AUTO_LAUNCH_SINGLE_CHOICE";
15 | }
16 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/android/content/pm/ComponentInfo.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.android.content.pm
2 |
3 | import android.content.ComponentName
4 | import android.content.pm.ComponentInfo
5 |
6 | fun ComponentInfo.getComponentName() = ComponentName(packageName, name)
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/android/content/pm/PackageManagerUtils.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.android.content.pm
2 |
3 | import android.content.ComponentName
4 | import android.content.Context
5 | import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
6 | import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
7 | import android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED
8 | import android.content.pm.PackageManager.DONT_KILL_APP
9 | import androidx.core.content.ContextCompat
10 |
11 | inline fun Context.getSystemService(): T? =
12 | ContextCompat.getSystemService(this, T::class.java)
13 |
14 | inline fun Context.disableComponent() =
15 | packageManager.setComponentEnabledSetting(ComponentName(this, T::class.java), COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP)
16 | inline fun Context.enableComponent() =
17 | packageManager.setComponentEnabledSetting(ComponentName(this, T::class.java), COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP)
18 | inline fun Context.resetComponentState() =
19 | packageManager.setComponentEnabledSetting(ComponentName(this, T::class.java), COMPONENT_ENABLED_STATE_DEFAULT, DONT_KILL_APP)
20 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/Config.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island;
2 |
3 | import com.google.firebase.remoteconfig.FirebaseRemoteConfig;
4 | import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings;
5 | import com.google.firebase.remoteconfig.FirebaseRemoteConfigValue;
6 | import com.oasisfeng.island.firebase.FirebaseWrapper;
7 | import com.oasisfeng.island.shared.BuildConfig;
8 | import com.oasisfeng.island.shared.R;
9 |
10 | /**
11 | * Remotely configurable values, (default values are defined in config_defaults.xml)
12 | *
13 | * Created by Oasis on 2016/5/26.
14 | */
15 | public enum Config {
16 |
17 | /* All keys must be consistent with config_defaults.xml */
18 | IS_REMOTE("is_remote"),
19 | URL_FAQ("url_faq"),
20 | URL_SETUP("url_setup"),
21 | URL_SETUP_GOD_MODE("url_setup_god_mode"),
22 | URL_SETUP_TROUBLESHOOTING("url_setup_trouble"),
23 | URL_FILE_SHUTTLE("url_file_shuttle"),
24 | PERMISSION_REQUEST_ALLOWED_APPS("permission_allowed_apps");
25 |
26 | public String get() {
27 | final FirebaseRemoteConfig config = FirebaseRemoteConfig.getInstance();
28 | config.activateFetched();
29 | return config.getString(key);
30 | }
31 |
32 | public static boolean isRemote() {
33 | final FirebaseRemoteConfig config = FirebaseRemoteConfig.getInstance();
34 | config.activateFetched();
35 | final FirebaseRemoteConfigValue value = config.getValue(IS_REMOTE.key);
36 | return value.getSource() == FirebaseRemoteConfig.VALUE_SOURCE_REMOTE;
37 | }
38 |
39 | Config(final String key) { this.key = key; }
40 |
41 | private final String key;
42 |
43 | static {
44 | FirebaseWrapper.init();
45 | final FirebaseRemoteConfigSettings settings = new FirebaseRemoteConfigSettings.Builder().setDeveloperModeEnabled(BuildConfig.DEBUG).build();
46 | final FirebaseRemoteConfig config = FirebaseRemoteConfig.getInstance();
47 | config.setConfigSettings(settings);
48 | config.setDefaults(R.xml.config_defaults);
49 | config.fetch();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/IslandApplication.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island;
2 |
3 | import android.app.Application;
4 |
5 | import com.oasisfeng.island.analytics.CrashReport;
6 |
7 | /**
8 | * For singleton instance purpose only.
9 | *
10 | * Created by Oasis on 2018/1/3.
11 | */
12 | public class IslandApplication extends Application {
13 |
14 | public static Application $() {
15 | return sInstance;
16 | }
17 |
18 | public IslandApplication() {
19 | if (sInstance != null) throw new IllegalStateException("Already initialized");
20 | sInstance = this;
21 | CrashReport.initCrashHandler();
22 | }
23 |
24 | private static IslandApplication sInstance;
25 | }
26 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/PersistentService.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island
2 |
3 | object PersistentService {
4 |
5 | const val SERVICE_INTERFACE = "com.oasisfeng.island.PersistentService"
6 | }
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/analytics/Analytics.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.analytics
2 |
3 | import android.os.Bundle
4 | import android.util.Log
5 | import androidx.annotation.CheckResult
6 | import androidx.annotation.Size
7 | import com.google.firebase.analytics.FirebaseAnalytics
8 | import com.oasisfeng.island.IslandApplication
9 | import org.intellij.lang.annotations.Pattern
10 |
11 | /**
12 | * Abstraction for analytics service
13 | *
14 | * Created by Oasis on 2016/5/26.
15 | */
16 | interface Analytics {
17 |
18 | interface Event {
19 | @CheckResult fun with(key: Param, value: String?) = withRaw(key.key, value)
20 | @CheckResult fun withRaw(key: String, value: String?): Event
21 | fun send()
22 | }
23 |
24 | enum class Param(@param:Pattern("^[a-zA-Z][a-zA-Z0-9_]*$") val key: String) {
25 | ITEM_ID(FirebaseAnalytics.Param.ITEM_ID),
26 | /** ITEM_CATEGORY and ITEM_NAME cannot be used together (limitation in Google Analytics implementation) */
27 | ITEM_NAME(FirebaseAnalytics.Param.ITEM_NAME),
28 | ITEM_CATEGORY(FirebaseAnalytics.Param.ITEM_CATEGORY),
29 | LOCATION(FirebaseAnalytics.Param.LOCATION),
30 | CONTENT(FirebaseAnalytics.Param.CONTENT);
31 | }
32 |
33 | @CheckResult fun event(@Size(min = 1, max = 40) @Pattern("^[a-zA-Z][a-zA-Z0-9_]*$") event: String): Event
34 | fun reportEvent(event: String, params: Bundle)
35 | fun trace(key: String, value: String): Analytics
36 | fun trace(key: String, value: Int): Analytics
37 | fun trace(key: String, value: Boolean): Analytics
38 | fun report(t: Throwable)
39 | fun report(message: String, t: Throwable)
40 | fun logAndReport(tag: String, message: String, t: Throwable) { Log.e(tag, message, t); report(message, t) }
41 |
42 | enum class Property(val key: String) {
43 | DeviceOwner("device_owner"),
44 | IslandSetup("island_setup"),
45 | EncryptionRequired("encryption_required"),
46 | DeviceEncrypted("device_encrypted"),
47 | RemoteConfigAvailable("remote_config_avail"),
48 | FileShuttleEnabled("file_shuttle_enabled");
49 |
50 | }
51 |
52 | fun setProperty(property: Property, @Size(max = 36) value: String)
53 | fun setProperty(property: Property, value: Boolean): Analytics = this.also { setProperty(property, value.toString()) }
54 |
55 | companion object {
56 | @JvmStatic fun log(tag: String, message: String) { Log.i(tag, message); CrashReport.log("[$tag] $message") }
57 | @JvmStatic @Suppress("FunctionName") fun `$`() = impl
58 | operator fun invoke() = impl
59 | private val impl: Analytics = AnalyticsImpl(IslandApplication.`$`())
60 | }
61 | }
62 |
63 | fun analytics(): Analytics = Analytics()
64 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/analytics/CrashReport.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.analytics;
2 |
3 | import android.os.Process;
4 |
5 | import androidx.annotation.NonNull;
6 |
7 | import com.google.firebase.FirebaseApp;
8 | import com.google.firebase.crashlytics.FirebaseCrashlytics;
9 | import com.oasisfeng.android.util.Suppliers;
10 | import com.oasisfeng.island.IslandApplication;
11 |
12 | import java.util.function.Supplier;
13 |
14 |
15 | /**
16 | * Lazy initializer for crash handler.
17 | *
18 | * Created by Oasis on 2017/7/14.
19 | */
20 | public abstract class CrashReport {
21 |
22 | static void logException(final Throwable t) { sSingleton.get().recordException(t); }
23 | static void log(final String message) { sSingleton.get().log(message); }
24 | static void setProperty(final String key, final String value) { sSingleton.get().setCustomKey(key, value); }
25 | static void setProperty(final String key, final int value) { sSingleton.get().setCustomKey(key, value); }
26 | static void setProperty(final String key, final boolean value) { sSingleton.get().setCustomKey(key, value); }
27 |
28 | private static final Supplier sSingleton = Suppliers.memoize(() -> {
29 | FirebaseApp.initializeApp(IslandApplication.$());
30 | final FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
31 | crashlytics.setCrashlyticsCollectionEnabled(true/*BuildConfig.CRASHLYTICS_ENABLED*/);
32 | crashlytics.setCustomKey("user", Process.myUserHandle().hashCode()); // Attach the current (Android) user ID to crash report.
33 | return crashlytics;
34 | });
35 |
36 | public static void initCrashHandler() {
37 | final Thread.UncaughtExceptionHandler current_exception_handler = Thread.getDefaultUncaughtExceptionHandler();
38 | if (! (current_exception_handler instanceof LazyThreadExceptionHandler))
39 | Thread.setDefaultUncaughtExceptionHandler(new LazyThreadExceptionHandler(current_exception_handler));
40 | }
41 |
42 | private static class LazyThreadExceptionHandler implements Thread.UncaughtExceptionHandler {
43 |
44 | @Override public void uncaughtException(final @NonNull Thread thread, final @NonNull Throwable e) {
45 | if (mHandlingUncaughtException) { // Avoid infinite recursion
46 | mOriginalHandler.uncaughtException(thread, e);
47 | return;
48 | }
49 | mHandlingUncaughtException = true;
50 |
51 | sSingleton.get(); // Initialize if not yet
52 |
53 | final Thread.UncaughtExceptionHandler handler = thread.getUncaughtExceptionHandler();
54 | if (handler != null) handler.uncaughtException(thread, e); // May re-enter this method if delegate is initialized above.
55 | mHandlingUncaughtException = false;
56 | }
57 |
58 | LazyThreadExceptionHandler(final Thread.UncaughtExceptionHandler default_handler) {
59 | mOriginalHandler = default_handler;
60 | }
61 |
62 | private final Thread.UncaughtExceptionHandler mOriginalHandler;
63 | private boolean mHandlingUncaughtException;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/data/helper/ApplicationInfoExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.data.helper
2 |
3 | import android.content.pm.ApplicationInfo
4 | import com.oasisfeng.island.util.Hacks
5 |
6 | inline val ApplicationInfo.installed: Boolean
7 | get() = flags and ApplicationInfo.FLAG_INSTALLED != 0
8 |
9 | inline val ApplicationInfo.isSystem: Boolean
10 | get() = flags and ApplicationInfo.FLAG_SYSTEM != 0
11 |
12 | inline val ApplicationInfo.suspended: Boolean
13 | get() = flags and ApplicationInfo.FLAG_SUSPENDED != 0
14 |
15 | inline val ApplicationInfo.hidden: Boolean
16 | get() = Hacks.ApplicationInfo_privateFlags[this]?.and(PRIVATE_FLAG_HIDDEN) == PRIVATE_FLAG_HIDDEN
17 |
18 | const val PRIVATE_FLAG_HIDDEN = 1
19 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/engine/CrossProfile.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.engine
2 |
3 | import android.app.admin.DevicePolicyManager.FLAG_PARENT_CAN_ACCESS_MANAGED
4 | import android.content.*
5 | import android.content.pm.PackageManager.MATCH_DEFAULT_ONLY
6 | import android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS
7 | import android.content.pm.ResolveInfo
8 | import com.oasisfeng.island.util.DevicePolicies
9 | import com.oasisfeng.island.util.Users
10 |
11 | object CrossProfile {
12 |
13 | const val CATEGORY_PARENT_PROFILE = "com.oasisfeng.island.category.PARENT_PROFILE"
14 | const val CATEGORY_MANAGED_PROFILE = "com.oasisfeng.island.category.MANAGED_PROFILE"
15 |
16 | /** The target activity must declare [CATEGORY_PARENT_PROFILE] and [Intent.CATEGORY_DEFAULT] in its intent-filter */
17 | @JvmStatic fun decorateIntentForActivityInParentProfile(context: Context, intent: Intent) {
18 | require(intent.data == null) { "Intent with data is not supported yet" }
19 | check(! Users.isOwner()) { "Must not be called in parent profile" }
20 | intent.addCategory(CATEGORY_PARENT_PROFILE)
21 | val candidates = queryActivities(context, intent)
22 | if (candidates.isEmpty()) throw ActivityNotFoundException("No matched activity for $intent")
23 | val forwarder = candidates.findForwarder()
24 | ?: (addRequiredForwarding(context, intent).let { queryActivities(context, intent).findForwarder() }
25 | ?: throw IllegalStateException("Failed to forward $intent"))
26 | intent.component = forwarder.activityInfo.run { ComponentName(packageName, name) }
27 | }
28 |
29 | private fun addRequiredForwarding(context: Context, intent: Intent) = DevicePolicies(context).addCrossProfileIntentFilter(
30 | IntentFilter(intent.action).apply { addCategory(CATEGORY_PARENT_PROFILE) }, FLAG_PARENT_CAN_ACCESS_MANAGED)
31 |
32 | private fun queryActivities(context: Context, intent: Intent)
33 | = context.packageManager.queryIntentActivities(intent, MATCH_DISABLED_COMPONENTS or MATCH_DEFAULT_ONLY) // Probably disabled in current profile
34 |
35 | private fun List.findForwarder()
36 | = firstOrNull { it.activityInfo.packageName == "android" }//?.activityInfo?.run { ComponentName(packageName, name) }
37 | }
38 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/engine/common/WellKnownPackages.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.engine.common;
2 |
3 | /**
4 | * Information about well known packages.
5 | *
6 | * Created by Oasis on 2017/3/11.
7 | */
8 | public class WellKnownPackages {
9 | public static final String PACKAGE_GOOGLE_PLAY_SERVICES = "com.google.android.gms";
10 | public static final String PACKAGE_GOOGLE_PLAY_STORE = "com.android.vending";
11 | public static final String PACKAGE_GOOGLE_CHROME = "com.android.chrome";
12 | }
13 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/installer/InstallerExtras.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.installer;
2 |
3 | /**
4 | * Internal interface of App Installer
5 | *
6 | * Created by Oasis on 2018-11-20.
7 | */
8 | public class InstallerExtras {
9 |
10 | public static final String EXTRA_APP_INFO = "appInfo"; // TODO: Migrate to ClipData-based splits API.
11 | }
12 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/notification/NotificationIds.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.notification
2 |
3 | import android.app.Notification
4 | import android.content.Context
5 |
6 | fun NotificationIds.post(context: Context, tag: String? = null, build: Notification.Builder.() -> Unit) =
7 | post(context, tag, @Suppress("DEPRECATION") Notification.Builder(context).also(build))
8 |
9 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/provisioning/CriticalAppsManager.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.provisioning;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.pm.ApplicationInfo;
5 | import android.content.pm.PackageManager;
6 | import android.os.IBinder;
7 |
8 | import androidx.annotation.Nullable;
9 |
10 | import com.oasisfeng.island.analytics.Analytics;
11 | import com.oasisfeng.island.engine.common.WellKnownPackages;
12 | import com.oasisfeng.island.util.Hacks;
13 | import com.oasisfeng.island.util.ProfileUser;
14 |
15 | import java.util.Set;
16 |
17 | import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
18 |
19 | /**
20 | * Detect critical apps (including system and installed ones).
21 | *
22 | * Created by Oasis on 2017/9/12.
23 | */
24 | public class CriticalAppsManager {
25 |
26 | @ProfileUser static Set detectCriticalPackages(final PackageManager pm) {
27 | final Set pkgs = SystemAppsManager.detectCriticalSystemPackages(pm);
28 |
29 | final String webview_pkg = getCurrentWebViewPackageName();
30 | if (webview_pkg != null) pkgs.add(webview_pkg);
31 | try { @SuppressLint("WrongConstant") // Chrome may not be current provider, since current provider may fallback to system WebView during provisioning.
32 | final ApplicationInfo chrome_info = pm.getApplicationInfo(WellKnownPackages.PACKAGE_GOOGLE_CHROME, MATCH_UNINSTALLED_PACKAGES);
33 | pkgs.add(chrome_info.packageName);
34 | } catch (final PackageManager.NameNotFoundException ignored) {}
35 | return pkgs;
36 | }
37 |
38 | public static @Nullable String getCurrentWebViewPackageName() {
39 | if (Hacks.ServiceManager_getService == null || Hacks.IWebViewUpdateService$Stub_asInterface == null
40 | || Hacks.IWebViewUpdateService_getCurrentWebViewPackageName == null) return null;
41 | try {
42 | final IBinder service = Hacks.ServiceManager_getService.invoke("webviewupdate").statically();
43 | if (service == null) throw new RuntimeException("Service not found: webviewupdate");
44 | final Object webview_service = Hacks.IWebViewUpdateService$Stub_asInterface.invoke(service).statically();
45 | return Hacks.IWebViewUpdateService_getCurrentWebViewPackageName.invoke().on(webview_service);
46 | } catch (final Exception e) {
47 | Analytics.$().logAndReport(TAG, "Error detecting WebView provider.", e);
48 | return null;
49 | }
50 | }
51 |
52 | private static final String TAG = "CriticalApps";
53 | }
54 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/settings/IslandSettings.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.settings
2 |
3 | import android.content.Context
4 | import android.database.ContentObserver
5 | import androidx.annotation.StringRes
6 | import com.oasisfeng.island.shared.R
7 | import com.oasisfeng.settings.AppSettings
8 |
9 | class IslandSettings(context: Context) {
10 |
11 | inner class DynamicShortcutLabel: BooleanSetting(R.string.setting_dynamic_shortcut_label)
12 |
13 | open inner class BooleanSetting(@StringRes prefKeyStringRes: Int, singleUser: Boolean = true)
14 | : IslandSetting(prefKeyStringRes, singleUser) {
15 | val enabled get() = mAppSettings.getBoolean(this)
16 | fun set(value: Boolean) = mAppSettings.set(this, value)
17 | }
18 |
19 | open inner class IslandSetting(override val prefKeyResId: Int, override val isSingleUser: Boolean) : AppSettings.AppSetting {
20 | /** Use [android.content.ContentResolver.unregisterContentObserver] to unregister. */
21 | fun registerObserver(observer: ContentObserver) = mAppSettings.registerObserver(this, observer)
22 | }
23 |
24 | val singleUserRootUri; get() = mAppSettings.singleUserRootUri
25 |
26 | private val mAppSettings = AppSettings(context)
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/shortcut/ShortcutIcons.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.shortcut;
2 |
3 | import android.app.ActivityManager;
4 | import android.content.Context;
5 | import android.graphics.Bitmap;
6 | import android.graphics.Canvas;
7 | import android.graphics.drawable.AdaptiveIconDrawable;
8 | import android.graphics.drawable.BitmapDrawable;
9 | import android.graphics.drawable.ColorDrawable;
10 | import android.graphics.drawable.Drawable;
11 | import android.graphics.drawable.Icon;
12 |
13 | import com.oasisfeng.android.ui.IconResizer;
14 | import com.oasisfeng.island.analytics.Analytics;
15 |
16 | import java.util.Objects;
17 |
18 | import androidx.annotation.Nullable;
19 | import androidx.annotation.RequiresApi;
20 |
21 | import static android.os.Build.VERSION_CODES.O;
22 | import static com.oasisfeng.island.analytics.Analytics.Param.ITEM_CATEGORY;
23 | import static com.oasisfeng.island.analytics.Analytics.Param.ITEM_ID;
24 |
25 | /**
26 | * Helper for shortcut icon.
27 | *
28 | * Created by Oasis on 2017/9/18.
29 | */
30 | public class ShortcutIcons {
31 |
32 | public static @Nullable Bitmap createLargeIconBitmap(final Context context, final Drawable drawable, final String pkg) {
33 | final int icon_size = Objects.requireNonNull((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getLauncherLargeIconSize();
34 | final Drawable icon = new IconResizer(icon_size).createIconThumbnail(drawable); // Resize the app icon in case it's too large. (also avoid TransactionTooLargeException)
35 | icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
36 | final Bitmap bitmap = drawableToBitmap(icon);
37 | if (bitmap == null) Analytics.$().event("invalid_app_icon").with(ITEM_ID, pkg).with(ITEM_CATEGORY, drawable.getClass().getName()).send();
38 | return bitmap;
39 | }
40 |
41 | @RequiresApi(O) static Icon createAdaptiveIcon(final AdaptiveIconDrawable drawable) {
42 | final int width = drawable.getIntrinsicWidth() * 3 / 2, height = drawable.getIntrinsicHeight() * 3 / 2,
43 | start = drawable.getIntrinsicWidth() / 4, top = drawable.getIntrinsicHeight() / 4;
44 | drawable.setBounds(start, top, width - start, height - top);
45 | final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
46 | final Canvas canvas = new Canvas(bitmap);
47 | drawable.draw(canvas);
48 | return Icon.createWithAdaptiveBitmap(bitmap);
49 | }
50 |
51 | private static Bitmap drawableToBitmap(final Drawable drawable) {
52 | if (drawable instanceof BitmapDrawable)
53 | return ((BitmapDrawable) drawable).getBitmap();
54 | if (drawable instanceof ColorDrawable) {
55 | return null; //TODO: Support color drawable
56 | }
57 | final Bitmap bitmap = Bitmap.createBitmap(drawable.getBounds().width(), drawable.getBounds().height(), Bitmap.Config.ARGB_8888);
58 | final Canvas canvas = new Canvas(bitmap);
59 | drawable.draw(canvas);
60 | return bitmap;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/shuttle/ActivityShuttle.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.shuttle;
2 |
3 | import android.content.ComponentName;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.pm.PackageManager;
7 | import android.content.pm.ResolveInfo;
8 |
9 | import java.util.List;
10 |
11 | /**
12 | * Shuttle for cross-profile activity behaviors.
13 | *
14 | * Created by Oasis on 2019-3-4.
15 | */
16 | public class ActivityShuttle {
17 |
18 | public static Intent forceNeverForwarding(final PackageManager pm, final Intent intent) {
19 | final List candidates = pm.queryIntentActivities(intent, 0);
20 | if (candidates != null) for (final ResolveInfo candidate : candidates) {
21 | if ("android".equals(candidate.activityInfo.packageName)) continue;
22 | return intent.setComponent(new ComponentName(candidate.activityInfo.packageName, candidate.activityInfo.name));
23 | }
24 | return intent;
25 | }
26 |
27 | public static ComponentName getForwarder(final Context context) {
28 | return selectForwarder(context.getPackageManager().queryIntentActivities(new Intent(ServiceShuttle.ACTION_BIND_SERVICE), 0));
29 | }
30 |
31 | private static ComponentName selectForwarder(final List candidates) {
32 | if (candidates != null) for (final ResolveInfo candidate : candidates) {
33 | if (candidate.activityInfo == null || ! "android".equals(candidate.activityInfo.packageName)) continue;
34 | return new ComponentName(candidate.activityInfo.packageName, candidate.activityInfo.name);
35 | }
36 | return null;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/shuttle/Closure.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.shuttle
2 |
3 | import android.content.Context
4 | import android.os.Parcel
5 | import android.os.Parcelable
6 | import java.lang.reflect.Modifier
7 |
8 | private typealias CtxFun = Context.() -> R
9 |
10 | internal class Closure(private val functionClass: Class>, private val variables: Array): Parcelable {
11 |
12 | fun invoke(context: Context): Any? {
13 | val constructor = functionClass.declaredConstructors[0].apply { isAccessible = true }
14 | val args: Array = constructor.parameterTypes.map(::getDefaultValue).toTypedArray()
15 | val variables = variables
16 | @Suppress("UNCHECKED_CAST") val block = constructor.newInstance(* args) as CtxFun<*>
17 | block.javaClass.getMemberFields().forEachIndexed { index, field -> // Constructor arguments do not matter, as all fields are replaced.
18 | field.set(block, when (field.type) {
19 | Context::class.java -> context
20 | Closure::class.java -> (variables[index] as? Closure)?.invoke(context)
21 | else -> variables[index] }) }
22 | return block(context)
23 | }
24 |
25 | constructor(procedure: CtxFun<*>): this(procedure.javaClass, extractVariablesFromFields(procedure)) {
26 | val constructors = javaClass.declaredConstructors
27 | require(constructors.isNotEmpty()) { "The method must have at least one constructor" }
28 | }
29 |
30 | private fun getDefaultValue(type: Class<*>)
31 | = if (type.isPrimitive) java.lang.reflect.Array.get(java.lang.reflect.Array.newInstance(type, 1), 0) else null
32 |
33 | override fun toString() = "Closure{${functionClass.name}}"
34 |
35 | override fun describeContents() = 0
36 | override fun writeToParcel(dest: Parcel, flags: Int) =
37 | dest.run { writeString(functionClass.name); writeArray(variables) }
38 | @Suppress("UNCHECKED_CAST") constructor(parcel: Parcel, cl: ClassLoader)
39 | : this(cl.loadClass(parcel.readString()) as Class>, parcel.readArray(cl)!!)
40 |
41 | companion object CREATOR : Parcelable.ClassLoaderCreator {
42 |
43 | override fun createFromParcel(parcel: Parcel, classLoader: ClassLoader) = Closure(parcel, classLoader)
44 | override fun createFromParcel(parcel: Parcel) = Closure(parcel, Closure::class.java.classLoader!!)
45 | override fun newArray(size: Int): Array = arrayOfNulls(size)
46 |
47 | // Automatically generated fields for captured variables, by compiler (indeterminate order)
48 | private fun extractVariablesFromFields(procedure: CtxFun<*>)
49 | = procedure.javaClass.getMemberFields().map { wrapIfNeeded(it.get(procedure)) }.toTypedArray()
50 |
51 | private fun Class.getMemberFields()
52 | = declaredFields.filter { if (Modifier.isStatic(it.modifiers)) false else { it.isAccessible = true; true }}
53 |
54 | private fun wrapIfNeeded(obj: Any?): Any? = if (obj is Context) null else obj
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/shuttle/ContextShuttle.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.shuttle;
2 |
3 | import android.content.Context;
4 | import android.content.pm.PackageManager;
5 | import android.content.pm.PackageManager.NameNotFoundException;
6 | import android.os.UserHandle;
7 |
8 | import androidx.annotation.Nullable;
9 | import androidx.annotation.RequiresPermission;
10 |
11 | import com.oasisfeng.android.content.pm.Permissions;
12 | import com.oasisfeng.island.util.Hacks;
13 |
14 | /**
15 | * Utility class for cross-user context related stuffs.
16 | *
17 | * Created by Oasis on 2017/9/1.
18 | */
19 | public class ContextShuttle {
20 |
21 | @RequiresPermission(Permissions.INTERACT_ACROSS_USERS)
22 | public static @Nullable PackageManager getPackageManagerAsUser(final Context context, final UserHandle user) {
23 | try {
24 | final Context user_context = createContextAsUser(context, user);
25 | return user_context != null ? user_context.getPackageManager() : null;
26 | } catch (final NameNotFoundException ignored) { return null; } // Should never happen
27 | }
28 |
29 | @RequiresPermission(Permissions.INTERACT_ACROSS_USERS)
30 | public static @Nullable Context createPackageContextAsUser(final Context context, final String pkg, final UserHandle user) throws NameNotFoundException {
31 | return Hacks.Context_createPackageContextAsUser.invoke(pkg, 0, user).on(context);
32 | }
33 |
34 | public static @Nullable Context createContextAsUser(final Context context, final UserHandle user) throws NameNotFoundException {
35 | return createPackageContextAsUser(context, "system", user);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/shuttle/MethodInvocation.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.shuttle;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 |
6 | /**
7 | * The detail of method invocation to be shuttled.
8 | *
9 | * Created by Oasis on 2017/4/2.
10 | */
11 | class MethodInvocation implements Parcelable {
12 |
13 | String clazz;
14 | Object[] args;
15 | Result result;
16 | Throwable throwable;
17 |
18 | MethodInvocation() {}
19 |
20 | private MethodInvocation(final Parcel in) {
21 | clazz = in.readString();
22 | args = in.readArray(getClass().getClassLoader());
23 | }
24 |
25 | void readFromParcel(final Parcel parcel) { //noinspection unchecked
26 | result = (Result) parcel.readValue(getClass().getClassLoader());
27 | throwable = (Throwable) parcel.readSerializable();
28 | }
29 |
30 | @Override public void writeToParcel(final Parcel dest, final int flags) {
31 | if ((flags & PARCELABLE_WRITE_RETURN_VALUE) == 0) {
32 | dest.writeString(clazz);
33 | dest.writeArray(args);
34 | } else {
35 | dest.writeValue(result);
36 | dest.writeSerializable(throwable);
37 | }
38 | }
39 |
40 | @Override public int describeContents() { return 0; }
41 |
42 | static final Creator CREATOR = new Creator() {
43 | @Override public MethodInvocation createFromParcel(final Parcel in) { return new MethodInvocation(in); }
44 | @Override public MethodInvocation[] newArray(final int size) { return new MethodInvocation[size]; }
45 | };
46 | }
47 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/shuttle/Shuttle.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.shuttle
2 |
3 | import android.content.Context
4 | import android.os.UserHandle
5 | import com.oasisfeng.island.util.Users
6 |
7 | class Shuttle(val context: Context, val to: UserHandle) {
8 |
9 | /** @return Job if launched in coroutine, otherwise null. */
10 | fun launch(function: Context.() -> Unit) = if (to == Users.current()) { function(context) } else shuttle(function)
11 |
12 | fun invoke(function: Context.() -> R)
13 | = if (to == Users.current()) context.function() else shuttle(function)
14 |
15 | /* Helpers to avoid redundant local variables. ("inline" is used to ensure only "Context.() -> R" function is shuttled) */
16 | inline fun launch(with: A, crossinline function: Context.(A) -> Unit) { launch { function(with) }}
17 | inline fun invoke(with: A, crossinline function: Context.(A) -> R)
18 | = invoke { this.function(with) }
19 |
20 | private fun shuttle(function: Context.() -> R): R {
21 | val result = ShuttleProvider.call(context, to, function)
22 | if (result.isNotReady()) throw IllegalStateException("Shuttle not ready")
23 | return result.get()
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/util/CallerAwareActivity.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.util;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.net.Uri;
6 | import android.os.IBinder;
7 | import android.util.Log;
8 |
9 | import androidx.annotation.Nullable;
10 |
11 | /**
12 | * Activity with enhanced {@link #getCallingPackage()}, which detects caller even if not started by {@link #startActivityForResult(Intent, int)}.
13 | *
14 | * Created by Oasis on 2019-2-27.
15 | */
16 | public abstract class CallerAwareActivity extends Activity {
17 |
18 | @Override public @Nullable String getCallingPackage() {
19 | final String caller = super.getCallingPackage();
20 | if (caller != null) return caller;
21 | return getCallingPackage(this);
22 | }
23 |
24 | public static @Nullable String getCallingPackage(final Activity activity) {
25 | Intent original_intent = null;
26 | final Intent intent = activity.getIntent();
27 | if (intent.hasExtra(Intent.EXTRA_REFERRER) || intent.hasExtra(Intent.EXTRA_REFERRER_NAME)) {
28 | original_intent = new Intent(activity.getIntent());
29 | intent.removeExtra(Intent.EXTRA_REFERRER);
30 | intent.removeExtra(Intent.EXTRA_REFERRER_NAME);
31 | }
32 | try {
33 | final Uri referrer = activity.getReferrer(); // getReferrer() returns real calling package if no referrer extras
34 | if (referrer != null) return referrer.getAuthority(); // Referrer URI: android-app://
35 | } finally {
36 | if (original_intent != null) activity.setIntent(original_intent);
37 | }
38 | if (Hacks.IActivityManager_getLaunchedFromPackage != null) try {
39 | final Object am = Hacks.ActivityManagerNative_getDefault.invoke().statically();
40 | if (am != null) {
41 | final IBinder token = Hacks.Activity_getActivityToken.invoke().on(activity);
42 | return token != null ? Hacks.IActivityManager_getLaunchedFromPackage.invoke(token).on(am) : null;
43 | }
44 | } catch (final Exception e) {
45 | Log.e(TAG, "Error detecting caller", e);
46 | }
47 | return null;
48 | }
49 |
50 | private static final String TAG = "Island.CAA";
51 | }
52 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/util/DeviceAdmins.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.util;
2 |
3 | import android.app.admin.DeviceAdminReceiver;
4 | import android.app.admin.DevicePolicyManager;
5 | import android.content.ComponentName;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.content.pm.PackageManager;
9 | import android.content.pm.ResolveInfo;
10 |
11 | import com.oasisfeng.island.analytics.Analytics;
12 | import com.oasisfeng.island.shared.BuildConfig;
13 |
14 | import java.util.List;
15 |
16 | import static android.content.Context.DEVICE_POLICY_SERVICE;
17 | import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
18 | import static android.content.pm.PackageManager.DONT_KILL_APP;
19 | import static com.oasisfeng.island.analytics.Analytics.Param.ITEM_ID;
20 | import static java.util.Objects.requireNonNull;
21 |
22 | /**
23 | * Utility class for device-admin related functions
24 | *
25 | * Created by Oasis on 2017/2/19.
26 | */
27 | public class DeviceAdmins {
28 |
29 | public static ComponentName getComponentName(final Context context) {
30 | if (sDeviceAdminComponent == null) sDeviceAdminComponent = queryComponentName(context);
31 | return sDeviceAdminComponent;
32 | }
33 |
34 | private static ComponentName queryComponentName(final Context context) {
35 | final List active_admins = requireNonNull((DevicePolicyManager) context.getSystemService(DEVICE_POLICY_SERVICE)).getActiveAdmins();
36 | if (active_admins != null && ! active_admins.isEmpty()) for (final ComponentName active_admin : active_admins)
37 | if (Modules.MODULE_ENGINE.equals(active_admin.getPackageName())) return active_admin;
38 |
39 | final Intent intent = new Intent(DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED).setPackage(Modules.MODULE_ENGINE);
40 | final List admins = context.getPackageManager().queryBroadcastReceivers(intent, PackageManager.GET_DISABLED_COMPONENTS);
41 | if (admins.size() == 1) {
42 | final ResolveInfo admin = admins.get(0);
43 | sDeviceAdminComponent = new ComponentName(Modules.MODULE_ENGINE, admins.get(0).activityInfo.name);
44 | if (! admin.activityInfo.enabled) {
45 | Analytics.$().event("device_admin_component_disabled").with(ITEM_ID, sDeviceAdminComponent.flattenToShortString()).send();
46 | context.getPackageManager().setComponentEnabledSetting(sDeviceAdminComponent, COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
47 | }
48 | return sDeviceAdminComponent;
49 | } // No resolve result on some Android 7.x devices, cause unknown.
50 | if (BuildConfig.DEBUG) throw new IllegalStateException("Engine module is not correctly installed: " + admins);
51 | return new ComponentName(Modules.MODULE_ENGINE, "com.oasisfeng.island.IslandDeviceAdminReceiver"); // Fallback
52 | }
53 |
54 | private static ComponentName sDeviceAdminComponent;
55 | }
56 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/util/DevicePolicies.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.util
2 |
3 | import android.app.admin.DevicePolicyManager
4 |
5 | typealias DPM = DevicePolicyManager
6 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/util/OwnerUser.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.util;
2 |
3 | import java.lang.annotation.Retention;
4 | import java.lang.annotation.Target;
5 |
6 | import static java.lang.annotation.ElementType.CONSTRUCTOR;
7 | import static java.lang.annotation.ElementType.FIELD;
8 | import static java.lang.annotation.ElementType.METHOD;
9 | import static java.lang.annotation.ElementType.TYPE;
10 | import static java.lang.annotation.RetentionPolicy.SOURCE;
11 |
12 | /**
13 | * Annotation to indicate running in owner user.
14 | *
15 | * Created by Oasis on 2016/11/27.
16 | */
17 | @Retention(SOURCE)
18 | @Target({TYPE,METHOD,CONSTRUCTOR,FIELD})
19 | public @interface OwnerUser {}
20 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/util/Permissions.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.util;
2 |
3 | import android.content.Context;
4 | import android.content.pm.PackageManager;
5 | import android.os.Process;
6 |
7 | import javax.annotation.ParametersAreNonnullByDefault;
8 |
9 | /**
10 | * Permission-related helpers
11 | *
12 | * Created by Oasis on 2017/10/8.
13 | */
14 | @ParametersAreNonnullByDefault
15 | public class Permissions {
16 |
17 | public static final String INTERACT_ACROSS_USERS = "android.permission.INTERACT_ACROSS_USERS";
18 |
19 | private static final int PID = Process.myPid();
20 | private static final int UID = Process.myUid();
21 |
22 | public static boolean has(final Context context, final String permission) {
23 | return context.checkPermission(permission, PID, UID) == PackageManager.PERMISSION_GRANTED;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/util/ProfileUser.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.util;
2 |
3 | import java.lang.annotation.Retention;
4 | import java.lang.annotation.Target;
5 |
6 | import static java.lang.annotation.ElementType.CONSTRUCTOR;
7 | import static java.lang.annotation.ElementType.FIELD;
8 | import static java.lang.annotation.ElementType.METHOD;
9 | import static java.lang.annotation.ElementType.TYPE;
10 | import static java.lang.annotation.RetentionPolicy.SOURCE;
11 |
12 | /**
13 | * Annotation to indicate running in profile user.
14 | *
15 | * Created by Oasis on 2016/11/27.
16 | */
17 | @Retention(SOURCE)
18 | @Target({TYPE,METHOD,CONSTRUCTOR,FIELD})
19 | public @interface ProfileUser {}
20 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/util/RomVariants.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.util
2 |
3 | object RomVariants {
4 |
5 | @JvmStatic fun isMiui() = ! Hacks.SystemProperties_get.invoke("ro.miui.ui.version.name").statically().isNullOrBlank()
6 | }
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/island/util/UserHandles.kt:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.island.util
2 |
3 | import android.os.UserHandle
4 |
5 | fun UserHandle.toId() = hashCode()
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/perf/Performances.java:
--------------------------------------------------------------------------------
1 | package com.oasisfeng.perf;
2 |
3 | import android.os.SystemClock;
4 | import android.util.Log;
5 |
6 | import com.oasisfeng.deagle.BuildConfig;
7 |
8 | import java.util.concurrent.TimeUnit;
9 |
10 | import androidx.annotation.Nullable;
11 |
12 | /**
13 | * Utility class for performance related stuffs
14 | *
15 | * Created by Oasis on 2016/9/25.
16 | */
17 | public class Performances {
18 |
19 | private static final boolean DEBUG = BuildConfig.DEBUG;
20 |
21 | public static final class Debug {
22 |
23 | public static @Nullable Stopwatch startUptimeStopwatch() {
24 | return DEBUG ? Stopwatch.createStarted(TICKER_UPTIME) : null;
25 | }
26 |
27 | public static @Nullable Stopwatch startThreadCpuTimeStopwatch() {
28 | return DEBUG ? Stopwatch.createStarted(TICKER_THREAD_CPU_TIME) : null;
29 | }
30 | }
31 |
32 | public static final Ticker TICKER_UPTIME = new Ticker() { @Override public long read() { return SystemClock.uptimeMillis() * 1_000_000L; }};
33 | public static final Ticker TICKER_THREAD_CPU_TIME = new Ticker() { @Override public long read() { return android.os.Debug.threadCpuTimeNanos(); }};
34 |
35 | public static @Nullable Stopwatch startUptimeStopwatch() {
36 | return Stopwatch.createStarted(TICKER_UPTIME);
37 | }
38 |
39 | public static @Nullable Stopwatch startThreadCpuTimeStopwatch() {
40 | return Stopwatch.createStarted(TICKER_THREAD_CPU_TIME);
41 | }
42 |
43 | public static void check(final @Nullable Stopwatch stopwatch, final long threshold_millis, final String what) {
44 | if (stopwatch == null) return;
45 | final long duration = stopwatch.elapsed(TimeUnit.MILLISECONDS);
46 | if (duration > threshold_millis) Log.w(TAG, duration + " ms spent in " + what);
47 | }
48 |
49 | private static final String TAG = "Performance";
50 | }
51 |
--------------------------------------------------------------------------------
/shared/src/main/java/com/oasisfeng/perf/Ticker.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 The Guava Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | package com.oasisfeng.perf;
16 |
17 | /**
18 | * A time source; returns a time value representing the number of nanoseconds elapsed since some
19 | * fixed but arbitrary point in time. Note that most users should use {@link Stopwatch} instead of
20 | * interacting with this class directly.
21 | *
22 | * Warning: this interface can only be used to measure elapsed time, not wall time.
23 | *
24 | * @author Kevin Bourrillion
25 | * @since 10.0 (mostly
26 | * source-compatible since 9.0)
27 | */
28 | public abstract class Ticker {
29 | /**
30 | * Constructor for use by subclasses.
31 | */
32 | protected Ticker() {}
33 |
34 | /**
35 | * Returns the number of nanoseconds elapsed since this ticker's fixed point of reference.
36 | */
37 | public abstract long read();
38 |
39 | /**
40 | * A ticker that reads the current time using {@link System#nanoTime}.
41 | *
42 | * @since 10.0
43 | */
44 | public static Ticker systemTicker() {
45 | return SYSTEM_TICKER;
46 | }
47 |
48 | private static final Ticker SYSTEM_TICKER = new Ticker() { @Override public long read() { return System.nanoTime(); }};
49 | }
50 |
--------------------------------------------------------------------------------
/shared/src/main/res/drawable/ic_landscape_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/shared/src/main/res/drawable/ic_settings_applications_white_24dp.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/shared/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #303F9F
5 | #1A237E
6 | #c67100
7 |
8 | @android:color/white
9 |
10 |
--------------------------------------------------------------------------------
/shared/src/main/res/values-pt-rBR/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Tarefa em andamento
4 | Importante
5 | Instalação do app
6 | Observador do Island
7 | Observador do app
8 | Depurar
9 |
10 | Para desativar esta restrição, veja a configuração relacionada do Island.
11 | Quando o Island é ativado, você pode ver \"Dispositivo é gerenciado por uma organização\" ou informação similar no IU do sistema. É um aviso padrão de transparência do Perfil de Trabalho (o recurso subjacente de Android usado pelo Island), que não pode ser removido.\n\nO Island não coleta suas informações pessoais de forma nenhuma, consulte nossa política de privacidade na Loja do Google Play para obter mais detalhes.\n\nSe receber uma mensagem \"xxx recurso desativado por admin\", neste caso você podia ter ativado um dos recursos de restrição no Island, por favor, consulte a configuração relacionada no Island.\n\nPara desinstalar o Island, você deve desativá-lo primeiro, vá para Island - Configurações - Configurações Avançadas - Destruir / Desativar.
12 |
13 | App Ops não foram preservados devido a um erro interno.
14 | A funcionalidade de configurações intercambiáveis é limitada
15 | Toque para restaurar
16 |
17 |
--------------------------------------------------------------------------------
/shared/src/main/res/values-v24/flags.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | false
4 |
5 |
--------------------------------------------------------------------------------
/shared/src/main/res/values-v26/flags.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 |
5 |
--------------------------------------------------------------------------------
/shared/src/main/res/values-v28/flags.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 |
5 |
--------------------------------------------------------------------------------
/shared/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 執行中的任務
4 | 重要事項
5 | 應用程式安裝
6 | 諸界 守望者
7 | 應用程式 守望者
8 | 偵錯
9 |
10 | 如需取消此限制,請至“煉妖壺”中調整對應的設定。\n\n應用程式權限:限制修改狀態表明此權限已被撤銷。如需恢復,請至“設定 - 分空間的獨立設定 - 隱私保護”。
11 | 您可能注意到系统界面中出现的“裝置由您所在的單位負責管理”或類似訊息,這是 Android 工作資料(壺中界的 Android 底層機制)啟用後的透明性告知,無法隱藏。\n\n我們不會以任何方式收集您的個人隐私訊息,請參考在 Google Play 商店中發布的隱私政策。\n\n如果遇到“XX 特性已被管理員停用”,可能是由於您打開了某項限制功能,請至此應用程式中調整對應的設定。
12 |
13 | App Ops 未能保留(內部異常)
14 |
--------------------------------------------------------------------------------
/shared/src/main/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 执行中的任务
4 | 重要事项
5 | 应用安装
6 | 诸界 守望者
7 | 应用 守望者
8 | 调试
9 |
10 | 如需取消此限制,请至“炼妖壶”中调整相应的设置。\n\n应用权限:限制修改状态表明此权限已被撤销。如需恢复,请至“设置 - 分空间的独立设置 - 隐私保护”。
11 | 您可能注意到系统界面中出现的“设备由您所在的单位负责管理”或类似信息,这是 Android 工作资料(壶中界的 Android 底层机制)激活后的透明性告知,无法隐去。\n\n我们不会以任何方式收集您的个人隐私信息,请参阅在 Google Play 市场中公示的隐私政策。\n\n如果遇到“XX 特性已被管理员禁用”,可能是由于您打开了某项限制功能,请至此应用中调整相应的设置。
12 |
13 | App Ops 未能保留(内部异常)
14 |
15 | 跨界功能受限
16 | 点此恢复
17 |
--------------------------------------------------------------------------------
/shared/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #3F51B5
5 | #303F9F
6 | #FFA000
7 |
8 | @android:color/black
9 |
10 |
--------------------------------------------------------------------------------
/shared/src/main/res/values/flags.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 | false
5 | false
6 |
7 |
--------------------------------------------------------------------------------
/shared/src/main/res/values/settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | dynamic_shortcut_label
4 |
--------------------------------------------------------------------------------
/shared/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Ongoing task
4 | Important
5 | App Installation
6 | Island Watcher
7 | App Watcher
8 | Debug
9 |
10 | To disable this restriction, please refer to corresponding setting in Island.
11 | When Island is activated, you may see \"device is managed by your organization\" or similar information around the system UI. It\'s the standard transparency notice of Android for Work (the underlying Android feature used by Island), which cannot be removed.\n\nIsland does not collect your personal information in any means, see our privacy policy on Google Play Store for details.\n\nIf you get message \"xxx feature disabled by admin\", you may have enabled one of the restriction features in Island, please refer to corresponding setting in Island.\n\nTo uninstall Island, you must deactivate it first, go to Island - Settings - Scoped Settings - Destroy / Deactivate.
12 |
13 | App Ops was not preserved due to internal error.
14 |
15 | Cross-land functionality is limited
16 | Tap to restore
17 |
--------------------------------------------------------------------------------
/shared/src/main/res/xml/analytics_tracker.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | UA-77327039-1
4 | 180
5 | false
6 | false
7 |
--------------------------------------------------------------------------------
/shared/src/main/res/xml/config_defaults.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | url_faq
5 | https://island.oasisfeng.com/faq
6 |
7 |
8 | url_setup
9 | https://island.oasisfeng.com/setup
10 |
11 |
12 | url_setup_god_mode
13 | https://island.oasisfeng.com/setup#manual-setup-for-island-in-god-mode
14 |
15 |
16 | url_setup_trouble
17 | https://island.oasisfeng.com/faq
18 |
19 |
20 | url_file_shuttle
21 | https://island.oasisfeng.com/files
22 |
23 |
24 | permission_allowed_apps
25 | com.oasisfeng.greenify,com.oasisfeng.nevo
26 |
27 |
28 |
--------------------------------------------------------------------------------
/watcher/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 | kotlinOptions.jvmTarget = "1.8"
13 |
14 | compileOptions {
15 | sourceCompatibility JavaVersion.VERSION_1_8
16 | targetCompatibility JavaVersion.VERSION_1_8
17 | }
18 | }
19 |
20 | dependencies {
21 | implementation project(':shared')
22 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
23 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$kotlin_coroutine_version"
24 |
25 | testImplementation 'junit:junit:4.12'
26 | androidTestImplementation 'androidx.test:runner:1.3.0'
27 | }
28 |
--------------------------------------------------------------------------------
/watcher/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
28 |
29 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/watcher/src/main/res/values-pt-rBR/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | O serviço do Island está ativo
3 | Desativar a fim de parar todas as atividades internas em segundo plano.
4 | %s está ativo
5 | Clique para suspender de novo
6 | Desativar
7 | Recomeçar
8 | Configurações
9 | Descartar
10 |
11 | Desativação falhou, por favor faça-o manualmente.
12 |
13 | Permissão \"%s\" foi concedida
14 | Gostaria de revogar esta permissão?
15 | Revogar
16 | Manter concedida
17 |
18 |
--------------------------------------------------------------------------------
/watcher/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | 壺中界 運轉中
3 | 封印它可終止其中所有的背景活動
4 | %s 已解凍
5 | 點擊將其解凍
6 | 封印
7 | 設定
8 | 忽略
9 |
10 | 停止工作資料失敗,請手動關閉。
11 |
12 | 已授權权限“%s”
13 | 是否撤銷此權限?
14 | 撤銷
15 | 保持授權
16 |
17 |
--------------------------------------------------------------------------------
/watcher/src/main/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | 壶中界 运转中
3 | 封印它可终止其中所有的潜在活动
4 | %s 已解冻
5 | 点击将其冻结
6 | 封印
7 | 重启
8 | 设置
9 | 忽略
10 |
11 | 停止工作资料失败,请手动关闭。
12 |
13 | 已授予权限“%s”
14 | 是否撤销此权限?
15 | 撤销
16 | 保持授予
17 |
18 |
--------------------------------------------------------------------------------
/watcher/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Island space is active
3 | Deactivate to cease all the background activities inside.
4 | %s is active
5 | Click to re-freeze
6 | Deactivate
7 | Restart
8 | Settings
9 | Dismiss
10 |
11 | Failed to deactivate, please toggle manually.
12 |
13 | Permission \"%s\" was granted
14 | Would you like to revoke this permission?
15 | Revoke
16 | Keep granted
17 |
18 |
--------------------------------------------------------------------------------