├── .github └── workflows │ └── android.yml ├── .gitignore ├── .idea ├── .gitignore ├── .name ├── AndroidProjectSystem.xml ├── appInsightsSettings.xml ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── deploymentTargetDropDown.xml ├── deploymentTargetSelector.xml ├── gradle.xml ├── kotlinc.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── src │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── sunshine │ │ │ └── freeform │ │ │ └── ExampleInstrumentedTest.kt │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── aidl │ │ │ ├── android │ │ │ │ ├── hardware │ │ │ │ │ └── input │ │ │ │ │ │ └── IInputManager.aidl │ │ │ │ └── view │ │ │ │ │ ├── IRotationWatcher.aidl │ │ │ │ │ ├── IWindowManager.aidl │ │ │ │ │ └── MotionEvent.aidl │ │ │ └── com │ │ │ │ └── sunshine │ │ │ │ └── freeform │ │ │ │ ├── IControlService.aidl │ │ │ │ ├── bean │ │ │ │ └── MotionEventBean.aidl │ │ │ │ └── callback │ │ │ │ ├── IAppRunningListener.aidl │ │ │ │ └── IOnRotationChangedListener.aidl │ │ ├── assets │ │ │ └── xposed_init │ │ ├── java │ │ │ └── com │ │ │ │ └── sunshine │ │ │ │ └── freeform │ │ │ │ ├── app │ │ │ │ └── MiFreeform.kt │ │ │ │ ├── bean │ │ │ │ └── MotionEventBean.java │ │ │ │ ├── broadcast │ │ │ │ ├── BootBroadcastReceiver.kt │ │ │ │ └── StartFreeformReceiver.kt │ │ │ │ ├── hook │ │ │ │ ├── HookFramework.kt │ │ │ │ ├── HookLauncher.kt │ │ │ │ ├── HookMyself.kt │ │ │ │ ├── HookSystemUI.kt │ │ │ │ └── utils │ │ │ │ │ ├── HookShellUtils.java │ │ │ │ │ ├── HookTest.java │ │ │ │ │ └── XLog.kt │ │ │ │ ├── room │ │ │ │ ├── DatabaseRepository.kt │ │ │ │ ├── FreeFormAppsDao.kt │ │ │ │ ├── FreeFormAppsEntity.kt │ │ │ │ ├── MyDatabase.kt │ │ │ │ ├── NotificationAppsDao.kt │ │ │ │ └── NotificationAppsEntity.kt │ │ │ │ ├── service │ │ │ │ ├── ControlService.kt │ │ │ │ ├── ForegroundService.kt │ │ │ │ ├── KeepAliveService.kt │ │ │ │ ├── QuickStartTileService.kt │ │ │ │ └── notification │ │ │ │ │ ├── NotificationIntentService.kt │ │ │ │ │ ├── NotificationService.kt │ │ │ │ │ └── NotificationViewModel.kt │ │ │ │ ├── systemapi │ │ │ │ ├── InputManager.kt │ │ │ │ ├── ServiceManager.kt │ │ │ │ ├── UserHandle.kt │ │ │ │ └── WindowManager.kt │ │ │ │ ├── ui │ │ │ │ ├── base │ │ │ │ │ └── BaseActivity.kt │ │ │ │ ├── choose_apps │ │ │ │ │ ├── AppsRecyclerAdapter.kt │ │ │ │ │ ├── ChooseAppsActivity.kt │ │ │ │ │ └── ChooseAppsViewModel.kt │ │ │ │ ├── floating │ │ │ │ │ ├── AllAppsAdapter.kt │ │ │ │ │ ├── ChooseAppFloatingAdapter.kt │ │ │ │ │ ├── ChooseAppFloatingView.kt │ │ │ │ │ ├── ChooseAppFloatingViewModel.kt │ │ │ │ │ ├── FloatingActivity.kt │ │ │ │ │ └── FloatingServiceHelper.kt │ │ │ │ ├── floating_apps_sort │ │ │ │ │ ├── AppsSortRecyclerAdapter.kt │ │ │ │ │ ├── FloatingAppsSortActivity.kt │ │ │ │ │ └── FloatingAppsSortModel.kt │ │ │ │ ├── freeform │ │ │ │ │ ├── FreeformConfig.kt │ │ │ │ │ ├── FreeformHelper.kt │ │ │ │ │ ├── FreeformService.kt │ │ │ │ │ ├── FreeformView.kt │ │ │ │ │ ├── FreeformViewAbs.kt │ │ │ │ │ ├── FreeformViewModel.kt │ │ │ │ │ ├── ScreenListener.kt │ │ │ │ │ └── StackSet.kt │ │ │ │ ├── guide │ │ │ │ │ ├── FirstFragment.kt │ │ │ │ │ ├── FreeformStudyViewNew.kt │ │ │ │ │ ├── GuideActivity.kt │ │ │ │ │ ├── GuideStudyActivity.kt │ │ │ │ │ └── SecondFragment.kt │ │ │ │ ├── main │ │ │ │ │ ├── HomeFragment.kt │ │ │ │ │ ├── MainActivity.kt │ │ │ │ │ └── SettingFragment.kt │ │ │ │ ├── permission │ │ │ │ │ └── PermissionActivity.kt │ │ │ │ ├── splash │ │ │ │ │ ├── SplashActivity.kt │ │ │ │ │ └── SplashViewModel.kt │ │ │ │ └── view │ │ │ │ │ ├── CircleImageView.kt │ │ │ │ │ ├── FreeformRootViewGroup.kt │ │ │ │ │ ├── IntegerSimpleMenuPreference.java │ │ │ │ │ ├── MTextView.kt │ │ │ │ │ └── WaveSideBarView.java │ │ │ │ └── utils │ │ │ │ ├── PackageUtils.kt │ │ │ │ ├── PermissionUtils.kt │ │ │ │ ├── ServiceUtils.kt │ │ │ │ └── ShellUtils.kt │ │ └── res │ │ │ ├── anim │ │ │ ├── hang_up_tip_fade_in.xml │ │ │ ├── hang_up_tip_fade_out.xml │ │ │ └── text_fade_in.xml │ │ │ ├── drawable-night │ │ │ ├── color_background.xml │ │ │ ├── ic_add.xml │ │ │ ├── ic_alipay.xml │ │ │ ├── ic_all.xml │ │ │ ├── ic_paypal.xml │ │ │ └── ic_wechat_pay.xml │ │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── drawable │ │ │ ├── bar_corners_bg.xml │ │ │ ├── bar_corners_bg_flyme.xml │ │ │ ├── color_background.xml │ │ │ ├── floating_button_bg.xml │ │ │ ├── floating_button_bg_right.xml │ │ │ ├── hang_up_tip_circle_left_bottom.xml │ │ │ ├── hang_up_tip_circle_left_up.xml │ │ │ ├── hang_up_tip_circle_right_bottom.xml │ │ │ ├── hang_up_tip_circle_right_up.xml │ │ │ ├── ic_add.xml │ │ │ ├── ic_alipay.xml │ │ │ ├── ic_all.xml │ │ │ ├── ic_api.xml │ │ │ ├── ic_arrow_forward.xml │ │ │ ├── ic_back.xml │ │ │ ├── ic_close.xml │ │ │ ├── ic_coolapk.xml │ │ │ ├── ic_done.xml │ │ │ ├── ic_error_white.xml │ │ │ ├── ic_github.xml │ │ │ ├── ic_guide.xml │ │ │ ├── ic_home.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── ic_money.xml │ │ │ ├── ic_paypal.xml │ │ │ ├── ic_qq.xml │ │ │ ├── ic_question.xml │ │ │ ├── ic_search.xml │ │ │ ├── ic_settings.xml │ │ │ ├── ic_star.xml │ │ │ ├── ic_telegram.xml │ │ │ ├── ic_wechat_pay.xml │ │ │ ├── tile_icon.png │ │ │ └── view_corners_bg.xml │ │ │ ├── layout │ │ │ ├── activity_choose_apps.xml │ │ │ ├── activity_floating.xml │ │ │ ├── activity_floating_apps_sort.xml │ │ │ ├── activity_freeform_guide.xml │ │ │ ├── activity_guide_study.xml │ │ │ ├── activity_main.xml │ │ │ ├── activity_permission.xml │ │ │ ├── activity_recent.xml │ │ │ ├── activity_splash.xml │ │ │ ├── content_main.xml │ │ │ ├── content_permission.xml │ │ │ ├── fragment_guide_first.xml │ │ │ ├── fragment_guide_second.xml │ │ │ ├── fragment_home.xml │ │ │ ├── item_app.xml │ │ │ ├── item_app2.xml │ │ │ ├── item_floating.xml │ │ │ ├── item_recent.xml │ │ │ ├── task_view_menu_option.xml │ │ │ ├── view_all_apps.xml │ │ │ ├── view_bar.xml │ │ │ ├── view_bar_flyme.xml │ │ │ ├── view_choose_app_floating.xml │ │ │ ├── view_choose_app_floting_view_recycler_app.xml │ │ │ ├── view_edit.xml │ │ │ ├── view_floating_button.xml │ │ │ ├── view_floating_button_left.xml │ │ │ ├── view_floating_button_right.xml │ │ │ ├── view_freeform.xml │ │ │ ├── view_freeform_flyme.xml │ │ │ ├── view_freeform_study.xml │ │ │ └── view_hang_up_tip.xml │ │ │ ├── menu │ │ │ ├── bottom_nav_menu.xml │ │ │ └── menu_choose_apps.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ └── launcher.xml │ │ │ ├── mipmap-xxhdpi │ │ │ └── wechat.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── navigation │ │ │ ├── nav_graph.xml │ │ │ └── nav_guide.xml │ │ │ ├── raw │ │ │ └── loading.json │ │ │ ├── values-night │ │ │ ├── colors.xml │ │ │ └── themes.xml │ │ │ ├── values-zh-rCN │ │ │ ├── arrays.xml │ │ │ └── strings.xml │ │ │ ├── values │ │ │ ├── arrays.xml │ │ │ ├── attrs.xml │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── ids.xml │ │ │ ├── strings.xml │ │ │ ├── styles.xml │ │ │ ├── themes.xml │ │ │ └── values-ar │ │ │ │ └── strings.xml │ │ │ └── xml │ │ │ ├── accessibility_config.xml │ │ │ ├── backup_rules.xml │ │ │ ├── data_extraction_rules.xml │ │ │ └── settings.xml │ └── test │ │ └── java │ │ └── com │ │ └── sunshine │ │ └── freeform │ │ └── ExampleUnitTest.kt └── update.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── hidden-api ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── aidl │ └── android │ │ ├── app │ │ ├── ActivityManager.aidl │ │ ├── IApplicationThread.aidl │ │ ├── IServiceConnection.aidl │ │ └── ITaskStackListener.aidl │ │ └── content │ │ ├── IIntentReceiver.aidl │ │ └── IIntentSender.aidl │ └── java │ └── android │ ├── app │ ├── ActivityOptionsHidden.java │ ├── IActivityManager.java │ ├── IActivityTaskManager.java │ ├── PendingIntentHidden.java │ ├── ProfilerInfo.java │ └── TaskStackListener.java │ └── content │ └── ContextHidden.java ├── open_api.md ├── open_api_zh-Hans.md ├── qa_zh-Hans.md ├── settings.gradle └── todo.md /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Release Build Automatically 2 | 3 | on: 4 | push: 5 | branches: [ "flyme" ] 6 | pull_request: 7 | branches: [ "flyme" ] 8 | workflow_dispatch: # Why not do it manually 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: set up JDK 17 18 | uses: actions/setup-java@v4 19 | with: 20 | java-version: '17' 21 | distribution: 'temurin' 22 | cache: gradle 23 | 24 | - name: Grant execute permission for gradlew 25 | run: chmod +x gradlew 26 | - name: Build with Gradle 27 | run: ./gradlew assembleDebug 28 | 29 | - uses: actions/upload-artifact@v4 30 | with: 31 | name: Android-output 32 | path: | 33 | ${{github.workspace}}/app/build/outputs/apk/release/*.apk 34 | ${{github.workspace}}/app/build/outputs/apk/debug/*.apk 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | # SpecStory explanation file 17 | .specstory/.what-is-this.md 18 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Flyme-Freeform -------------------------------------------------------------------------------- /.idea/AndroidProjectSystem.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/appInsightsSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 25 | 26 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/deploymentTargetSelector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flyme-FreeForm 2 | 3 | 4 | 5 | Mi-FreeForm is an APP that is activated through Shizuku/Sui and can display most apps in the form of freeform. Current support: 6 | - Open the favorites app in small window mode through the global sidebar 7 | - Open the favorites app with resident notifications 8 | - Open the favorites app with a tile 9 | - Make the APP that sends notifications open in freeform mode 10 | 11 | ## Download 12 | [Release](https://github.com/Live-block/Flyme-FreeForm/releases/) 13 | 14 | ## Library 15 | [AppIconLoader](https://github.com/zhanghai/AppIconLoader) 16 | 17 | [Glide](https://github.com/bumptech/glide) 18 | 19 | [RikkaX](https://github.com/RikkaApps/RikkaX) 20 | 21 | [Shizuku](https://github.com/RikkaApps/Shizuku) 22 | 23 | [TinyPinyin](https://github.com/promeG/TinyPinyin) 24 | 25 | [Xposed](https://github.com/rovo89/Xposed) 26 | 27 | ## License 28 | ``` 29 | Copyright (C) 2021-2022 sunshine0523 30 | Copyright (C) 2023 DtHnAme 31 | Copyright (C) 2024-2025 Live-block 32 | 33 | This program is free software: you can redistribute it and/or modify 34 | it under the terms of the GNU General Public License as published by 35 | the Free Software Foundation, either version 3 of the License, or 36 | (at your option) any later version. 37 | 38 | This program is distributed in the hope that it will be useful, 39 | but WITHOUT ANY WARRANTY; without even the implied warranty of 40 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 41 | GNU General Public License for more details. 42 | ``` 43 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | # 混淆会导致序列化失败 24 | -keep class com.sunshine.freeform.bean.MotionEventBean {*;} 25 | # 不混淆需要hook的类 26 | -keep class com.sunshine.freeform.hook.HookFramework {*;} 27 | -keep class com.sunshine.freeform.hook.HookMyself {*;} 28 | -keep class com.sunshine.freeform.hook.HookSystemUI {*;} 29 | -keep class com.sunshine.freeform.hook.utils.HookShellUtils {*;} 30 | -keep class com.sunshine.freeform.hook.**{*;} 31 | 32 | #避免对AIDL混淆 33 | -keep class * implements android.os.IInterface {*;} 34 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/sunshine/freeform/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.sunshine.freeform", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/aidl/android/hardware/input/IInputManager.aidl: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 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 android.hardware.input; 18 | 19 | interface IInputManager { 20 | // Injects an input event into the system. To inject into windows owned by other 21 | // applications, the caller must have the INJECT_EVENTS permission. 22 | boolean injectInputEvent(in InputEvent ev, int mode); 23 | } -------------------------------------------------------------------------------- /app/src/main/aidl/android/view/IRotationWatcher.aidl: -------------------------------------------------------------------------------- 1 | /* //device/java/android/android/hardware/ISensorListener.aidl 2 | ** 3 | ** Copyright 2008, The Android Open Source Project 4 | ** 5 | ** Licensed under the Apache License, Version 2.0 (the "License"); 6 | ** you may not use this file except in compliance with the License. 7 | ** You may obtain a copy of the License at 8 | ** 9 | ** http://www.apache.org/licenses/LICENSE-2.0 10 | ** 11 | ** Unless required by applicable law or agreed to in writing, software 12 | ** distributed under the License is distributed on an "AS IS" BASIS, 13 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ** See the License for the specific language governing permissions and 15 | ** limitations under the License. 16 | */ 17 | 18 | package android.view; 19 | 20 | /** 21 | * {@hide} 22 | */ 23 | interface IRotationWatcher { 24 | oneway void onRotationChanged(int rotation); 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/aidl/android/view/IWindowManager.aidl: -------------------------------------------------------------------------------- 1 | /* 2 | ** Copyright 2006, 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 android.view; 18 | 19 | import android.view.IRotationWatcher; 20 | 21 | interface IWindowManager { 22 | /** 23 | * Lock the display orientation to the specified rotation, or to the current 24 | * rotation if -1. Sensor input will be ignored until thawRotation() is called. 25 | * 26 | * @param displayId the ID of display which rotation should be frozen. 27 | * @param rotation one of {@link android.view.Surface#ROTATION_0}, 28 | * {@link android.view.Surface#ROTATION_90}, {@link android.view.Surface#ROTATION_180}, 29 | * {@link android.view.Surface#ROTATION_270} or -1 to freeze it to current rotation. 30 | * @hide 31 | */ 32 | void freezeDisplayRotation(int displayId, int rotation); 33 | 34 | /** 35 | * Release the orientation lock imposed by freezeRotation() on the display. 36 | * 37 | * @param displayId the ID of display which rotation should be thawed. 38 | * @hide 39 | */ 40 | void thawDisplayRotation(int displayId); 41 | 42 | /** 43 | * Gets whether the rotation is frozen on the display. 44 | * 45 | * @param displayId the ID of display which frozen is needed. 46 | * @return Whether the rotation is frozen. 47 | */ 48 | boolean isDisplayRotationFrozen(int displayId); 49 | 50 | /** 51 | * Watch the rotation of the specified screen. Returns the current rotation, 52 | * calls back when it changes. 53 | */ 54 | int watchRotation(IRotationWatcher watcher, int displayId); 55 | 56 | /** 57 | * Remove a rotation watcher set using watchRotation. 58 | */ 59 | void removeRotationWatcher(IRotationWatcher watcher); 60 | } -------------------------------------------------------------------------------- /app/src/main/aidl/android/view/MotionEvent.aidl: -------------------------------------------------------------------------------- 1 | package android.view; 2 | 3 | parcelable MotionEvent; -------------------------------------------------------------------------------- /app/src/main/aidl/com/sunshine/freeform/IControlService.aidl: -------------------------------------------------------------------------------- 1 | // IControlService.aidl 2 | package com.sunshine.freeform; 3 | 4 | // Declare any non-default types here with import statements 5 | import com.sunshine.freeform.bean.MotionEventBean; 6 | import com.sunshine.freeform.callback.IOnRotationChangedListener; 7 | import com.sunshine.freeform.callback.IAppRunningListener; 8 | import android.view.MotionEvent; 9 | 10 | interface IControlService { 11 | boolean init(); 12 | void pressBack(int displayId); 13 | void touch(in MotionEventBean motionEventBean); 14 | boolean moveStack(int displayId); 15 | boolean execShell(String command, boolean useRoot); 16 | } -------------------------------------------------------------------------------- /app/src/main/aidl/com/sunshine/freeform/bean/MotionEventBean.aidl: -------------------------------------------------------------------------------- 1 | // MotionEventBean.aidl 2 | package com.sunshine.freeform.bean; 3 | 4 | // Declare any non-default types here with import statements 5 | 6 | parcelable MotionEventBean; -------------------------------------------------------------------------------- /app/src/main/aidl/com/sunshine/freeform/callback/IAppRunningListener.aidl: -------------------------------------------------------------------------------- 1 | // IAppRunningListener.aidl 2 | package com.sunshine.freeform.callback; 3 | 4 | // Declare any non-default types here with import statements 5 | 6 | interface IAppRunningListener { 7 | /** 8 | * Demonstrates some basic types that you can use as parameters 9 | * and return values in AIDL. 10 | */ 11 | void onAppStop(); 12 | } -------------------------------------------------------------------------------- /app/src/main/aidl/com/sunshine/freeform/callback/IOnRotationChangedListener.aidl: -------------------------------------------------------------------------------- 1 | // IOnRotationChangedListener.aidl 2 | package com.sunshine.freeform.callback; 3 | 4 | // Declare any non-default types here with import statements 5 | 6 | interface IOnRotationChangedListener { 7 | void onRotationChanged(int rotation); 8 | } -------------------------------------------------------------------------------- /app/src/main/assets/xposed_init: -------------------------------------------------------------------------------- 1 | com.sunshine.freeform.hook.HookFramework 2 | com.sunshine.freeform.hook.HookSystemUI 3 | com.sunshine.freeform.hook.HookMyself 4 | com.sunshine.freeform.hook.HookLauncher -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/bean/MotionEventBean.java: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.bean; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * @author sunshine 10 | * @date 2021/3/14 11 | */ 12 | public class MotionEventBean implements Parcelable, Serializable { 13 | 14 | private static final long serialVersionUID = -7373576824258678549L; 15 | 16 | // 17 | private int action; 18 | 19 | private float[] xArray; 20 | 21 | private float[] yArray; 22 | 23 | private int displayId; 24 | 25 | @Override 26 | public int describeContents() { 27 | return 0; 28 | } 29 | 30 | @Override 31 | public void writeToParcel(Parcel dest, int flags) { 32 | dest.writeInt(this.action); 33 | dest.writeFloatArray(this.xArray); 34 | dest.writeFloatArray(this.yArray); 35 | dest.writeInt(this.displayId); 36 | } 37 | 38 | public void readFromParcel(Parcel source) { 39 | this.action = source.readInt(); 40 | this.xArray = source.createFloatArray(); 41 | this.yArray = source.createFloatArray(); 42 | this.displayId = source.readInt(); 43 | } 44 | 45 | public MotionEventBean(int action, float[] xArray, float[] yArray, int displayId) { 46 | this.action = action; 47 | this.xArray = xArray; 48 | this.yArray = yArray; 49 | this.displayId = displayId; 50 | } 51 | 52 | protected MotionEventBean(Parcel in) { 53 | this.action = in.readInt(); 54 | this.xArray = in.createFloatArray(); 55 | this.yArray = in.createFloatArray(); 56 | this.displayId = in.readInt(); 57 | } 58 | 59 | public static final Creator CREATOR = new Creator() { 60 | @Override 61 | public MotionEventBean createFromParcel(Parcel source) { 62 | return new MotionEventBean(source); 63 | } 64 | 65 | @Override 66 | public MotionEventBean[] newArray(int size) { 67 | return new MotionEventBean[size]; 68 | } 69 | }; 70 | 71 | public int getAction() { 72 | return action; 73 | } 74 | 75 | public void setAction(int action) { 76 | this.action = action; 77 | } 78 | 79 | public float[] getXArray() { 80 | return xArray; 81 | } 82 | 83 | public void setXArray(float[] xArray) { 84 | this.xArray = xArray; 85 | } 86 | 87 | public float[] getYArray() { 88 | return yArray; 89 | } 90 | 91 | public void setYArray(float[] yArray) { 92 | this.yArray = yArray; 93 | } 94 | 95 | public int getDisplayId() { 96 | return displayId; 97 | } 98 | 99 | public void setDisplayId(int displayId) { 100 | this.displayId = displayId; 101 | } 102 | 103 | 104 | } 105 | -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/broadcast/BootBroadcastReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.broadcast 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.widget.Toast 7 | import com.sunshine.freeform.app.MiFreeform 8 | import com.sunshine.freeform.service.ForegroundService 9 | import com.sunshine.freeform.service.KeepAliveService 10 | 11 | /** 12 | * @author sunshine 13 | * @date 2021/3/8 14 | */ 15 | class BootBroadcastReceiver : BroadcastReceiver() { 16 | 17 | override fun onReceive(context: Context, intent: Intent) { 18 | val sp = context.getSharedPreferences(MiFreeform.APP_SETTINGS_NAME, Context.MODE_PRIVATE) 19 | if (intent.action == action_boot) { 20 | if (sp.getInt("service_type", KeepAliveService.SERVICE_TYPE) == ForegroundService.SERVICE_TYPE) 21 | context.startForegroundService(Intent(context, ForegroundService::class.java)) 22 | } 23 | } 24 | 25 | companion object { 26 | const val action_boot = "android.intent.action.BOOT_COMPLETED" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/broadcast/StartFreeformReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.broadcast 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.ComponentName 5 | import android.content.Context 6 | import android.content.Intent 7 | import android.os.Parcelable 8 | import com.sunshine.freeform.ui.freeform.FreeformService 9 | 10 | class StartFreeformReceiver : BroadcastReceiver() { 11 | override fun onReceive(context: Context, intent: Intent) { 12 | val packageName = intent.getStringExtra("packageName") 13 | val activityName = intent.getStringExtra("activityName") 14 | val userId = intent.getIntExtra("userId", -1) 15 | val extras = intent.getStringExtra("extras") 16 | val parcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT) 17 | var target = Intent() 18 | if (packageName != null && activityName != null) 19 | target = Intent(Intent.ACTION_MAIN) 20 | .setComponent(ComponentName(packageName, activityName)) 21 | .setPackage(packageName) 22 | .addCategory(Intent.CATEGORY_LAUNCHER) 23 | if (parcelable is Intent) 24 | target = parcelable 25 | context.startService( 26 | Intent(context, FreeformService::class.java) 27 | .setAction(FreeformService.ACTION_START_INTENT) 28 | .putExtra(Intent.EXTRA_INTENT, target) 29 | .putExtra(Intent.EXTRA_COMPONENT_NAME, target.component) 30 | .putExtra(Intent.EXTRA_USER, userId) 31 | ) 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/hook/HookMyself.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.hook 2 | 3 | import com.sunshine.freeform.hook.utils.XLog 4 | import de.robv.android.xposed.IXposedHookLoadPackage 5 | import de.robv.android.xposed.XC_MethodHook 6 | import de.robv.android.xposed.XC_MethodReplacement 7 | import de.robv.android.xposed.XposedHelpers 8 | import de.robv.android.xposed.callbacks.XC_LoadPackage 9 | 10 | class HookMyself : IXposedHookLoadPackage { 11 | override fun handleLoadPackage(p0: XC_LoadPackage.LoadPackageParam) { 12 | if (p0.packageName == "com.sunshine.freeform") { 13 | // XposedHelpers.findAndHookMethod( 14 | // "com.sunshine.freeform.ui.permission.PermissionActivity", 15 | // p0.classLoader, 16 | // "checkXposedPermission", 17 | // Boolean::class.javaPrimitiveType, 18 | // object : XC_MethodHook() { 19 | // override fun beforeHookedMethod(param: MethodHookParam) { 20 | // param.args[0] = true 21 | // } 22 | // } 23 | // ) 24 | // XposedHelpers.findAndHookMethod( 25 | // "com.sunshine.freeform.ui.main.HomeFragment", 26 | // p0.classLoader, 27 | // "checkXposedPermission", 28 | // Boolean::class.javaPrimitiveType, 29 | // object : XC_MethodHook() { 30 | // override fun beforeHookedMethod(param: MethodHookParam) { 31 | // param.args[0] = true 32 | // } 33 | // } 34 | // ) 35 | XposedHelpers.findAndHookMethod( 36 | "com.sunshine.freeform.hook.utils.HookTest", 37 | p0.classLoader, 38 | "checkXposed", 39 | object : XC_MethodHook() { 40 | override fun beforeHookedMethod(param: MethodHookParam) { 41 | param.result = true 42 | } 43 | } 44 | ) 45 | } 46 | } 47 | 48 | 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/hook/HookSystemUI.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.hook 2 | 3 | import android.os.Build 4 | import de.robv.android.xposed.IXposedHookLoadPackage 5 | import de.robv.android.xposed.XC_MethodHook 6 | import de.robv.android.xposed.XposedBridge 7 | import de.robv.android.xposed.XposedHelpers 8 | import de.robv.android.xposed.callbacks.XC_LoadPackage 9 | 10 | /** 11 | * @author sunshine 12 | * @date 2021/2/28 13 | * 经过排查,小窗内旋转屏幕系统UI闪退是因为DisplayLayout中调用set方法会为空,那么,就将上次的参数保存,如果遇到空,就设置给这个 14 | */ 15 | class HookSystemUI : IXposedHookLoadPackage { 16 | override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam?) { 17 | //只有Android 11上才有 18 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && lpparam?.packageName == "com.android.systemui") { 19 | val clazz = XposedHelpers.findClass("com.android.systemui.wm.DisplayLayout", lpparam.classLoader) 20 | var lastObj: Any? = null 21 | XposedBridge.hookAllMethods(clazz, "set", object : XC_MethodHook() { 22 | override fun beforeHookedMethod(param: MethodHookParam?) { 23 | if (param != null) { 24 | val obj = param.args[0] 25 | if (obj != null) { 26 | lastObj = obj 27 | } else { 28 | param.args[0] = lastObj 29 | } 30 | } 31 | } 32 | }) 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/hook/utils/HookTest.java: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.hook.utils; 2 | 3 | public class HookTest { 4 | public static boolean checkXposed() { 5 | return false; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/hook/utils/XLog.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.hook.utils 2 | 3 | import de.robv.android.xposed.XposedBridge 4 | 5 | object XLog { 6 | 7 | fun d(s: Any) { 8 | XposedBridge.log("[Mi-Freeform/D] $s") 9 | } 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/room/DatabaseRepository.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.room 2 | 3 | import android.content.Context 4 | import androidx.lifecycle.LiveData 5 | import com.sunshine.freeform.room.MyDatabase.Companion.getDatabase 6 | import kotlinx.coroutines.flow.Flow 7 | import java.lang.Exception 8 | 9 | /** 10 | * @author sunshine 11 | * @date 2021/1/31 12 | */ 13 | class DatabaseRepository(context: Context) { 14 | 15 | private val freeFormAppsDao: FreeFormAppsDao 16 | private val notificationAppsDao: NotificationAppsDao 17 | 18 | fun insertFreeForm(packageName: String, userId: Int) { 19 | try { 20 | freeFormAppsDao.insert(packageName, userId) 21 | }catch (e: Exception) { } 22 | } 23 | 24 | fun deleteFreeForm(packageName: String, userId: Int) { 25 | freeFormAppsDao.delete(packageName, userId) 26 | } 27 | 28 | fun getAllFreeFormName(): LiveData?> { 29 | return freeFormAppsDao.getAllName() 30 | } 31 | 32 | fun getAllFreeForm() : LiveData?> { 33 | return freeFormAppsDao.getAll() 34 | } 35 | 36 | fun getAllFreeFormAppsByFlow(): Flow?> { 37 | return freeFormAppsDao.getAllByFlow() 38 | } 39 | 40 | fun getCount(): Int { 41 | return freeFormAppsDao.getCount() 42 | } 43 | 44 | fun update(entity: FreeFormAppsEntity) { 45 | freeFormAppsDao.update(entity) 46 | } 47 | 48 | fun getAllFreeFormWithoutLiveData() : List? { 49 | return freeFormAppsDao.getAllWithoutLiveData() 50 | } 51 | 52 | fun deleteAllFreeForm() { 53 | freeFormAppsDao.deleteAll() 54 | } 55 | 56 | fun deleteMore(freeFormAppsEntityList: List) { 57 | freeFormAppsDao.deleteList(freeFormAppsEntityList) 58 | } 59 | 60 | fun insertNotification(packageName: String, userId: Int) { 61 | try { 62 | notificationAppsDao.insert(packageName, userId) 63 | }catch (e: Exception) {} 64 | 65 | } 66 | 67 | fun deleteNotification(packageName: String, userId: Int) { 68 | notificationAppsDao.delete(packageName, userId) 69 | } 70 | 71 | fun getAllNotification(): LiveData?> { 72 | return notificationAppsDao.getAll() 73 | } 74 | 75 | fun getAllNotificationByFlow(): Flow?> { 76 | return notificationAppsDao.getAllByFlow() 77 | } 78 | 79 | fun deleteAllNotification() { 80 | notificationAppsDao.deleteAll() 81 | } 82 | 83 | init { 84 | val database = getDatabase(context) 85 | freeFormAppsDao = database.freeFormAppsDao 86 | notificationAppsDao = database.notificationAppsDao 87 | } 88 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/room/FreeFormAppsDao.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.room 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.room.Dao 5 | import androidx.room.Delete 6 | import androidx.room.Query 7 | import androidx.room.Update 8 | import kotlinx.coroutines.flow.Flow 9 | 10 | /** 11 | * @author sunshine 12 | * @date 2021/1/31 13 | */ 14 | @Dao 15 | interface FreeFormAppsDao { 16 | 17 | @Query("INSERT INTO FreeFormAppsEntity(packageName, userId) VALUES(:packageName, :userId)") 18 | fun insert(packageName: String, userId: Int) 19 | 20 | @Query("DELETE FROM FreeFormAppsEntity WHERE packageName = :packageName and userId = :userId") 21 | fun delete(packageName: String, userId: Int) 22 | 23 | @Query("SELECT * FROM FreeFormAppsEntity") 24 | fun getAll() : LiveData?> 25 | 26 | @Query("SELECT * FROM FreeFormAppsEntity") 27 | fun getAllByFlow() : Flow?> 28 | 29 | @Query("SELECT packageName FROM FreeFormAppsEntity") 30 | fun getAllName() : LiveData?> 31 | 32 | @Query("SELECT packageName FROM FreeFormAppsEntity") 33 | fun getAllWithoutLiveData() : List? 34 | 35 | @Query("SELECT COUNT(*) FROM FreeFormAppsEntity") 36 | fun getCount(): Int 37 | 38 | @Query("DELETE FROM FreeFormAppsEntity") 39 | fun deleteAll() 40 | 41 | @Delete 42 | fun deleteList(freeFormAppsEntityList: List) 43 | 44 | @Update 45 | fun update(entity: FreeFormAppsEntity) 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/room/FreeFormAppsEntity.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.room 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | 6 | 7 | /** 8 | * @author sunshine 9 | * @date 2021/1/31 10 | * 开启小窗数据库实体类 11 | */ 12 | @Entity 13 | class FreeFormAppsEntity( 14 | //用于排序 15 | @PrimaryKey(autoGenerate = true) 16 | val sortNum: Int, 17 | var packageName: String, 18 | //20210604新增用户标识 19 | var userId: Int = 0 20 | ) { 21 | override fun toString(): String { 22 | return "FreeFormAppsEntity(sortNum=$sortNum, packageName='$packageName')" 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/room/MyDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.room 2 | 3 | import android.content.Context 4 | import androidx.room.Database 5 | import androidx.room.Room 6 | import androidx.room.RoomDatabase 7 | import androidx.room.migration.Migration 8 | import androidx.sqlite.db.SupportSQLiteDatabase 9 | 10 | /** 11 | * @author sunshine 12 | * @date 2021/1/31 13 | */ 14 | @Database(entities = [FreeFormAppsEntity::class, NotificationAppsEntity::class], version = 5, exportSchema = false) 15 | abstract class MyDatabase : RoomDatabase() { 16 | abstract val freeFormAppsDao: FreeFormAppsDao 17 | abstract val notificationAppsDao: NotificationAppsDao 18 | 19 | companion object { 20 | private var database: MyDatabase? = null 21 | 22 | @Synchronized 23 | fun getDatabase(context: Context): MyDatabase { 24 | if (database == null) { 25 | database = Room.databaseBuilder(context.applicationContext, MyDatabase::class.java, "database.db") 26 | .allowMainThreadQueries() 27 | .addMigrations(MIGRATION_1_2) 28 | .addMigrations(MIGRATION_2_3) 29 | .addMigrations(MIGRATION_3_4) 30 | .addMigrations(MIGRATION_4_5) 31 | .build() 32 | } 33 | return database!! 34 | } 35 | 36 | private val MIGRATION_1_2 = object : Migration(1, 2) { 37 | override fun migrate(database: SupportSQLiteDatabase) { 38 | database.execSQL( 39 | "CREATE TABLE IF NOT EXISTS" + 40 | "'CompatibleAppsEntity'" + 41 | "('packageName' TEXT NOT NULL, PRIMARY KEY('packageName'))" 42 | ) 43 | } 44 | } 45 | 46 | private val MIGRATION_2_3 = object : Migration(2, 3) { 47 | override fun migrate(database: SupportSQLiteDatabase) { 48 | database.execSQL( 49 | "DROP TABLE IF EXISTS 'CompatibleAppsEntity'" 50 | ) 51 | database.execSQL( 52 | "ALTER TABLE 'FreeFormAppsEntity'" + 53 | "ADD 'sortNum' int NOT NULL DEFAULT 0" 54 | ) 55 | } 56 | } 57 | 58 | private val MIGRATION_3_4 = object : Migration(3, 4) { 59 | override fun migrate(database: SupportSQLiteDatabase) { 60 | database.execSQL( 61 | "ALTER TABLE 'FreeFormAppsEntity' RENAME TO 'FreeFormAppsEntity_OLD'" 62 | ) 63 | database.execSQL( 64 | "CREATE TABLE IF NOT EXISTS" + 65 | "'FreeFormAppsEntity'" + 66 | "('sortNum' INTEGER NOT NULL, 'packageName' TEXT NOT NULL, PRIMARY KEY('sortNum'))" 67 | ) 68 | database.execSQL( 69 | "INSERT INTO 'FreeFormAppsEntity'('packageName')" + 70 | "SELECT packageName from 'FreeFormAppsEntity_OLD'" 71 | ) 72 | database.execSQL( 73 | "DROP TABLE 'FreeFormAppsEntity_OLD'" 74 | ) 75 | } 76 | } 77 | 78 | private val MIGRATION_4_5 = object : Migration(4, 5) { 79 | override fun migrate(database: SupportSQLiteDatabase) { 80 | database.execSQL( 81 | "ALTER TABLE 'FreeFormAppsEntity' ADD COLUMN 'userId' INTEGER NOT NULL DEFAULT 0" 82 | ) 83 | database.execSQL( 84 | "ALTER TABLE 'NotificationAppsEntity' ADD COLUMN 'userId' INTEGER NOT NULL DEFAULT 0" 85 | ) 86 | } 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/room/NotificationAppsDao.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.room 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.room.Dao 5 | import androidx.room.Query 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | /** 9 | * @author sunshine 10 | * @date 2021/1/31 11 | */ 12 | @Dao 13 | interface NotificationAppsDao { 14 | 15 | @Query("INSERT INTO NotificationAppsEntity(packageName, userId) VALUES(:packageName, :userId)") 16 | fun insert(packageName: String, userId: Int) 17 | 18 | @Query("DELETE FROM NotificationAppsEntity WHERE packageName = :packageName and userId = :userId") 19 | fun delete(packageName: String, userId: Int) 20 | 21 | @Query("SELECT * FROM NotificationAppsEntity") 22 | fun getAll() : LiveData?> 23 | 24 | @Query("SELECT * FROM NotificationAppsEntity") 25 | fun getAllByFlow() : Flow?> 26 | 27 | @Query("DELETE FROM NotificationAppsEntity") 28 | fun deleteAll() 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/room/NotificationAppsEntity.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.room 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | 6 | 7 | /** 8 | * @author sunshine 9 | * @date 2021/1/31 10 | * 开启小窗数据库实体类 11 | */ 12 | @Entity 13 | class NotificationAppsEntity( 14 | // @PrimaryKey(autoGenerate = true) 15 | // val id: Int, 16 | @PrimaryKey 17 | val packageName: String, 18 | //20210604新增用户标识 19 | val userId: Int = 0 20 | ) 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/service/QuickStartTileService.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.service 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.net.Uri 7 | import android.os.Build 8 | import android.provider.Settings 9 | import android.service.quicksettings.TileService 10 | import android.widget.Toast 11 | import androidx.annotation.RequiresApi 12 | import androidx.preference.PreferenceManager 13 | import com.sunshine.freeform.R 14 | import com.sunshine.freeform.app.MiFreeform 15 | import com.sunshine.freeform.ui.floating.ChooseAppFloatingView 16 | import com.sunshine.freeform.ui.floating.FloatingActivity 17 | import kotlinx.coroutines.DelicateCoroutinesApi 18 | import java.io.DataOutputStream 19 | import java.lang.reflect.Method 20 | 21 | /** 22 | * @author sunshine 23 | * @date 2021/2/27 24 | * 通知栏快捷磁贴服务 25 | */ 26 | @DelicateCoroutinesApi 27 | class QuickStartTileService : TileService() { 28 | 29 | override fun onClick() { 30 | super.onClick() 31 | startActivity(Intent(this, FloatingActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) 32 | collapseStatusBar() 33 | } 34 | 35 | @SuppressLint("WrongConstant") 36 | private fun collapseStatusBar() { 37 | if (Build.VERSION.SDK_INT >= 31) { 38 | MiFreeform.me.execShell("cmd statusbar collapse", false) 39 | } else { 40 | val service = getSystemService("statusbar") ?: return 41 | try { 42 | val clazz = Class.forName("android.app.StatusBarManager") 43 | val collapse: Method? = clazz.getMethod("collapsePanels") 44 | collapse?.isAccessible = true 45 | collapse?.invoke(service) 46 | } catch (e: Exception) { 47 | e.printStackTrace() 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/service/notification/NotificationViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.service.notification 2 | 3 | import android.content.Context 4 | import com.sunshine.freeform.room.DatabaseRepository 5 | import com.sunshine.freeform.room.NotificationAppsEntity 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | /** 9 | * @author sunshine 10 | * @date 2022/1/6 11 | */ 12 | class NotificationViewModel(context: Context) { 13 | private val repository = DatabaseRepository(context) 14 | 15 | fun getAllNotificationApps(): Flow?> { 16 | return repository.getAllNotificationByFlow() 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/systemapi/InputManager.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.systemapi 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.IInterface 5 | import android.view.InputEvent 6 | import java.lang.reflect.InvocationTargetException 7 | import java.lang.reflect.Method 8 | 9 | /** 10 | * @author sunshine 11 | * @date 2021/2/10 12 | */ 13 | @SuppressLint("DiscouragedPrivateApi", "PrivateApi") 14 | class InputManager(private val manager: IInterface) { 15 | 16 | private var injectInputEventMethod: Method? = null 17 | private var setDisplayIdMethod: Method? = null 18 | 19 | private fun getInjectInputEventMethod(): Method? { 20 | try { 21 | if (injectInputEventMethod == null) { 22 | injectInputEventMethod = manager.javaClass.getMethod( 23 | "injectInputEvent", 24 | InputEvent::class.java, 25 | Int::class.javaPrimitiveType 26 | ) 27 | } 28 | }catch (e: Exception) { 29 | e.printStackTrace() 30 | } 31 | return injectInputEventMethod 32 | } 33 | 34 | fun injectInputEvent(inputEvent: InputEvent, displayId: Int): Boolean { 35 | setDisplayId(inputEvent, displayId) 36 | return try { 37 | val method = getInjectInputEventMethod() 38 | method!!.invoke(manager, inputEvent, 0) as Boolean 39 | } catch (e: InvocationTargetException) { 40 | e.printStackTrace() 41 | false 42 | } catch (e: IllegalAccessException) { 43 | e.printStackTrace() 44 | false 45 | } catch (e: NoSuchMethodException) { 46 | e.printStackTrace() 47 | false 48 | } 49 | } 50 | 51 | /** 52 | * 反射获取setDisplayId(int)方法 53 | */ 54 | private fun getSetDisplayIdMethod(): Method? { 55 | if (setDisplayIdMethod == null) { 56 | setDisplayIdMethod = InputEvent::class.java.getMethod( 57 | "setDisplayId", 58 | Int::class.javaPrimitiveType 59 | ) 60 | } 61 | return setDisplayIdMethod 62 | } 63 | 64 | /** 65 | * 设置displayId 66 | * 将副屏id传入 67 | * 用于控制副屏 68 | */ 69 | private fun setDisplayId(inputEvent: InputEvent, displayId: Int): Boolean { 70 | return try { 71 | val method = 72 | getSetDisplayIdMethod() 73 | method?.invoke(inputEvent, displayId) 74 | true 75 | }catch (e: Exception) { 76 | e.printStackTrace() 77 | false 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/systemapi/ServiceManager.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.systemapi 2 | 3 | import android.annotation.SuppressLint 4 | import android.os.IBinder 5 | import android.os.IInterface 6 | import java.lang.reflect.Method 7 | 8 | @SuppressLint("DiscouragedPrivateApi") 9 | class ServiceManager { 10 | private var inputManager: InputManager? = null 11 | private var windowManager: WindowManager? = null 12 | 13 | private val getServiceMethod: Method? = try { 14 | Class.forName("android.os.ServiceManager").getDeclaredMethod("getService", String::class.java) 15 | } catch (e: Exception) { 16 | e.printStackTrace() 17 | null 18 | } 19 | 20 | private fun getService(service: String, type: String): IInterface? { 21 | return try { 22 | val binder = getServiceMethod!!.invoke(null, service) as IBinder 23 | val asInterfaceMethod = Class.forName("$type\$Stub").getMethod( 24 | "asInterface", 25 | IBinder::class.java 26 | ) 27 | asInterfaceMethod.invoke(null, binder) as IInterface 28 | } catch (e: Exception) { 29 | e.printStackTrace() 30 | null 31 | } 32 | } 33 | 34 | fun getInputManager(): InputManager? { 35 | if (inputManager == null) { 36 | val inputManagerServer = getService("input", "android.hardware.input.IInputManager") 37 | if (inputManagerServer == null) return null 38 | else inputManager = InputManager(inputManagerServer) 39 | } 40 | return inputManager 41 | } 42 | 43 | fun getWindowManager(): WindowManager? { 44 | if (windowManager == null) { 45 | windowManager = WindowManager(getService("window", "android.view.IWindowManager")!!) 46 | } 47 | return windowManager 48 | } 49 | 50 | 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/systemapi/UserHandle.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.systemapi 2 | 3 | import android.os.UserHandle 4 | import android.util.Log 5 | 6 | /** 7 | * @author sunshine 8 | * @date 2021/6/5 9 | */ 10 | object UserHandle { 11 | 12 | /** 13 | * 通过uid获取userId 14 | */ 15 | fun getUserId(userHandle: UserHandle, uid: Int): Int { 16 | return try { 17 | userHandle::class.java.getMethod("getUserId", Int::class.javaPrimitiveType).invoke(userHandle, uid) as Int 18 | } catch (e: Exception) { 19 | 0 20 | } 21 | } 22 | 23 | fun getUserId(userHandle: UserHandle): Int { 24 | return try { 25 | val mHandleField = userHandle::class.java.getDeclaredField("mHandle") 26 | mHandleField.isAccessible = true 27 | mHandleField.get(userHandle) as Int 28 | } catch (e: Exception) { 29 | e.printStackTrace() 30 | 0 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/systemapi/WindowManager.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.systemapi 2 | 3 | import android.os.IInterface 4 | import android.view.IRotationWatcher 5 | import java.lang.AssertionError 6 | import java.lang.Exception 7 | 8 | class WindowManager(private val manager: IInterface) { 9 | // method changed since this commit: 10 | // https://android.googlesource.com/platform/frameworks/base/+/8ee7285128c3843401d4c4d0412cd66e86ba49e3%5E%21/#F2 11 | val rotation: Int 12 | get() = try { 13 | val cls: Class<*> = manager.javaClass 14 | try { 15 | manager.javaClass.getMethod("getRotation").invoke(manager) as Int 16 | } catch (e: NoSuchMethodException) { 17 | // method changed since this commit: 18 | // https://android.googlesource.com/platform/frameworks/base/+/8ee7285128c3843401d4c4d0412cd66e86ba49e3%5E%21/#F2 19 | cls.getMethod("getDefaultDisplayRotation").invoke(manager) as Int 20 | } 21 | } catch (e: Exception) { 22 | throw AssertionError(e) 23 | } 24 | 25 | fun registerRotationWatcher(rotationWatcher: IRotationWatcher?) { 26 | try { 27 | val cls: Class<*> = manager.javaClass 28 | try { 29 | cls.getMethod("watchRotation", IRotationWatcher::class.java) 30 | .invoke(manager, rotationWatcher) 31 | } catch (e: NoSuchMethodException) { 32 | // display parameter added since this commit: 33 | // https://android.googlesource.com/platform/frameworks/base/+/35fa3c26adcb5f6577849fd0df5228b1f67cf2c6%5E%21/#F1 34 | cls.getMethod( 35 | "watchRotation", IRotationWatcher::class.java, Int::class.javaPrimitiveType 36 | ).invoke(manager, rotationWatcher, 0) 37 | } 38 | } catch (e: Exception) { 39 | throw AssertionError(e) 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/base/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.base 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | 5 | class BaseActivity : AppCompatActivity() { 6 | 7 | 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/choose_apps/ChooseAppsViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.choose_apps 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import android.content.pm.LauncherActivityInfo 6 | import android.os.UserManager 7 | import androidx.lifecycle.AndroidViewModel 8 | import androidx.lifecycle.LiveData 9 | import com.sunshine.freeform.app.MiFreeform 10 | import com.sunshine.freeform.room.DatabaseRepository 11 | import com.sunshine.freeform.room.FreeFormAppsEntity 12 | import com.sunshine.freeform.room.NotificationAppsEntity 13 | import com.sunshine.freeform.systemapi.UserHandle 14 | 15 | /** 16 | * @author sunshine 17 | * @date 2021/1/31 18 | */ 19 | class ChooseAppsViewModel(application: Application) : AndroidViewModel(application){ 20 | 21 | private val repository = DatabaseRepository(application) 22 | private val sp = application.getSharedPreferences(MiFreeform.APP_SETTINGS_NAME, Context.MODE_PRIVATE) 23 | 24 | var type = 1 25 | 26 | fun getAllApps(): LiveData?> { 27 | return repository.getAllFreeForm() 28 | } 29 | 30 | fun getAllNotificationApps(): LiveData?> { 31 | return repository.getAllNotification() 32 | } 33 | 34 | fun insertApps(packageName: String, userId: Int) { 35 | when (type) { 36 | 2 -> { 37 | repository.insertNotification(packageName, userId) 38 | notifyNotificationAppsChanged() 39 | } 40 | else -> repository.insertFreeForm(packageName, userId) 41 | } 42 | } 43 | 44 | fun deleteApps(packageName: String, userId: Int) { 45 | when (type) { 46 | 2 -> { 47 | repository.deleteNotification(packageName, userId) 48 | notifyNotificationAppsChanged() 49 | } 50 | else -> { 51 | repository.deleteFreeForm(packageName, userId) 52 | } 53 | } 54 | } 55 | 56 | fun deleteAll() { 57 | when (type) { 58 | 2 -> { 59 | repository.deleteAllNotification() 60 | notifyNotificationAppsChanged() 61 | } 62 | 1 -> { 63 | repository.deleteAllFreeForm() 64 | } 65 | } 66 | } 67 | 68 | //添加列表中所有软件 69 | fun insertAllApps(allAppsList: ArrayList, userManager: UserManager) { 70 | deleteAll() 71 | allAppsList.forEach { 72 | when (type) { 73 | 2 -> { 74 | repository.insertNotification(it.applicationInfo.packageName, UserHandle.getUserId(it.user, it.applicationInfo.uid)) 75 | notifyNotificationAppsChanged() 76 | } 77 | else -> repository.insertFreeForm(it.applicationInfo.packageName, UserHandle.getUserId(it.user, it.applicationInfo.uid)) 78 | } 79 | } 80 | } 81 | 82 | private fun notifyNotificationAppsChanged() { 83 | putBoolean("notify_freeform_changed", !getBoolean("notify_freeform_changed", false)) 84 | } 85 | 86 | private fun putBoolean(key: String, newValue: Boolean) { 87 | sp.edit().putBoolean(key, newValue).apply() 88 | } 89 | 90 | private fun getBoolean(key: String, default: Boolean): Boolean { 91 | return sp.getBoolean(key, default) 92 | } 93 | 94 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/floating/ChooseAppFloatingViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.floating 2 | 3 | import android.content.Context 4 | import com.sunshine.freeform.room.DatabaseRepository 5 | import com.sunshine.freeform.room.FreeFormAppsEntity 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | /** 9 | * @author sunshine 10 | * @date 2022/1/6 11 | */ 12 | class ChooseAppFloatingViewModel(context: Context) { 13 | private val repository = DatabaseRepository(context) 14 | 15 | fun getAllFreeFormApps(): Flow?> { 16 | return repository.getAllFreeFormAppsByFlow() 17 | } 18 | 19 | fun deleteNotInstall(notInstallList: List) { 20 | repository.deleteMore(notInstallList) 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/floating/FloatingActivity.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.floating 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import androidx.appcompat.app.AppCompatActivity 6 | import android.os.Bundle 7 | import android.widget.Toast 8 | import com.sunshine.freeform.R 9 | import com.sunshine.freeform.app.MiFreeform 10 | import com.sunshine.freeform.service.ForegroundService 11 | import com.sunshine.freeform.service.KeepAliveService 12 | import com.sunshine.freeform.utils.PermissionUtils 13 | import com.sunshine.freeform.utils.ServiceUtils 14 | 15 | /** 16 | * 通过活动打开应用选择 17 | */ 18 | class FloatingActivity : AppCompatActivity() { 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | super.onCreate(savedInstanceState) 21 | setContentView(R.layout.activity_floating) 22 | 23 | val sp = getSharedPreferences(MiFreeform.APP_SETTINGS_NAME, Context.MODE_PRIVATE) 24 | when (sp.getInt("service_type", KeepAliveService.SERVICE_TYPE)) { 25 | KeepAliveService.SERVICE_TYPE -> { 26 | if (PermissionUtils.isAccessibilitySettingsOn(this)) { 27 | sp.edit().putBoolean("to_show_floating", !sp.getBoolean("to_show_floating", false)).apply() 28 | } else { 29 | Toast.makeText(this, getString(R.string.require_accessibility), Toast.LENGTH_SHORT).show() 30 | } 31 | } 32 | ForegroundService.SERVICE_TYPE -> { 33 | if (ServiceUtils.isServiceWork(this, "com.sunshine.freeform.service.ForegroundService")) { 34 | sp.edit().putBoolean("to_show_floating", !sp.getBoolean("to_show_floating", false)).apply() 35 | } else { 36 | startForegroundService(Intent(this, ForegroundService::class.java)) 37 | if (ServiceUtils.isServiceWork(this, "com.sunshine.freeform.service.ForegroundService")) { 38 | sp.edit().putBoolean("to_show_floating", !sp.getBoolean("to_show_floating", false)).apply() 39 | } else { 40 | Toast.makeText(this, getString(R.string.require_foreground), Toast.LENGTH_SHORT).show() 41 | } 42 | } 43 | } 44 | } 45 | 46 | finish() 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/floating/FloatingServiceHelper.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.floating 2 | 3 | /** 4 | * @author sunshine 5 | * @date 2022/09/17 6 | * 服务中悬浮窗帮助类,尽可能保证其单例且稳定 7 | */ 8 | object FloatingServiceHelper { 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/floating_apps_sort/AppsSortRecyclerAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.floating_apps_sort 2 | 3 | import android.content.Context 4 | import android.content.pm.ApplicationInfo 5 | import android.content.pm.LauncherApps 6 | import android.content.pm.PackageManager 7 | import android.os.Build 8 | import android.os.UserHandle 9 | import android.os.UserManager 10 | import android.view.LayoutInflater 11 | import android.view.View 12 | import android.view.ViewGroup 13 | import android.widget.ImageView 14 | import androidx.annotation.RequiresApi 15 | import androidx.appcompat.widget.AppCompatTextView 16 | import androidx.appcompat.widget.SwitchCompat 17 | import androidx.recyclerview.widget.RecyclerView 18 | import com.bumptech.glide.Glide 19 | import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions 20 | import com.sunshine.freeform.R 21 | import com.sunshine.freeform.room.FreeFormAppsEntity 22 | 23 | /** 24 | * @author sunshine 25 | * @date 2021/1/31 26 | * 小窗应用回收布局适配器 27 | */ 28 | class AppsSortRecyclerAdapter( 29 | private val pm: PackageManager, 30 | private val appsList: ArrayList 31 | ) : RecyclerView.Adapter() { 32 | 33 | private lateinit var context: Context 34 | private lateinit var launcherApps: LauncherApps 35 | private val userHandleMap = HashMap() 36 | 37 | class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 38 | val view: View = itemView.findViewById(R.id.view_app_click) 39 | val icon: ImageView = itemView.findViewById(R.id.imageView_app_icon) 40 | val name: AppCompatTextView = itemView.findViewById(R.id.textView_app_name) 41 | val switch: SwitchCompat = itemView.findViewById(R.id.switch_app) 42 | } 43 | 44 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 45 | context = parent.context 46 | val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager 47 | launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps 48 | 49 | userManager.userProfiles.forEach { 50 | userHandleMap[com.sunshine.freeform.systemapi.UserHandle.getUserId(it)] = it 51 | } 52 | 53 | return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_app, parent, false)) 54 | } 55 | 56 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 57 | //val applicationInfo = pm.getApplicationInfo(appsList[position].packageName, 0) 58 | val userHandle = if (userHandleMap.containsKey(appsList[position].userId)) userHandleMap[appsList[position].userId]!! else userHandleMap[0]!! 59 | val applicationInfo = launcherApps.getApplicationInfo(appsList[position].packageName, 0, userHandle) 60 | Glide.with(context) 61 | .load(applicationInfo.loadIcon(context.packageManager)) 62 | .transition(DrawableTransitionOptions.withCrossFade()) 63 | .into(holder.icon) 64 | holder.name.text = getLabel(applicationInfo, appsList[position].userId) 65 | holder.switch.visibility = View.GONE 66 | holder.view.setOnClickListener { } 67 | } 68 | 69 | private fun getLabel(applicationInfo: ApplicationInfo, userId: Int): CharSequence { 70 | return if (userId == 0) { 71 | pm.getApplicationLabel(applicationInfo) 72 | } else { 73 | "${pm.getApplicationLabel(applicationInfo)}-分身${userId}" 74 | } 75 | } 76 | 77 | override fun getItemCount(): Int { 78 | return appsList.size 79 | } 80 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/floating_apps_sort/FloatingAppsSortModel.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.floating_apps_sort 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import androidx.lifecycle.AndroidViewModel 6 | import androidx.lifecycle.LiveData 7 | import com.sunshine.freeform.room.DatabaseRepository 8 | import com.sunshine.freeform.room.FreeFormAppsEntity 9 | 10 | /** 11 | * @author sunshine 12 | * @date 2021/3/7 13 | */ 14 | class FloatingAppsSortModel(application: Application) : AndroidViewModel(application) { 15 | private val repository = DatabaseRepository(application) 16 | 17 | fun getAllApps(): LiveData?> { 18 | return repository.getAllFreeForm() 19 | } 20 | 21 | fun update(entity: FreeFormAppsEntity) { 22 | repository.update(entity) 23 | } 24 | 25 | fun deleteNotInstall(notInstallList: List) { 26 | repository.deleteMore(notInstallList) 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/freeform/FreeformConfig.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.freeform 2 | 3 | import android.content.ComponentName 4 | import android.os.Parcelable 5 | 6 | data class FreeformConfig( 7 | // Component Name 8 | var componentName: ComponentName? = null, 9 | //启动的userId 10 | var userId: Int = -1, 11 | // Intent 12 | var intent: Parcelable? = null, 13 | //叠加层最大高度 14 | var maxHeight: Int = 1, 15 | //分辨率 16 | var freeformDpi: Int = 1, 17 | //宽高比,默认9:16 18 | var widthHeightRatio: Float = 9f / 16f, 19 | //使用shizuku/sui阻止小窗跳出到全屏 20 | var useSuiRefuseToFullScreen: Boolean = false, 21 | // 降低背景亮度 22 | var dimAmount: Float = 0.3f, 23 | //兼容模式启动 24 | @Deprecated("", ReplaceWith("")) 25 | var compatibleMode: Boolean = false, 26 | var rememberPosition: Boolean = false, 27 | // 挂起大小 28 | var floatViewSize: Float = 0.2f, 29 | // 30 | var freeformSize: Float = 0.75f, 31 | var freeformSizeLand: Float = 0.9f, 32 | //记录启动位置 33 | var rememberX: Int = 0, 34 | var rememberY: Int = 0, 35 | //手动调整小窗方向 36 | var manualAdjustFreeformRotation: Boolean = false, 37 | ) 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/freeform/FreeformHelper.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.freeform 2 | 3 | import android.content.Context 4 | import android.view.Surface 5 | 6 | /** 7 | * @date 2022/8/22 8 | * @author sunshine0523 9 | * 小窗帮助类 10 | */ 11 | object FreeformHelper { 12 | fun getScreenDpi(context: Context): Int { 13 | return context.resources.displayMetrics.densityDpi 14 | } 15 | 16 | /** 17 | * @param rotation 屏幕方向是否是竖屏 18 | * @see Surface.ROTATION_0 19 | * @return orientation 20 | */ 21 | fun screenIsPortrait(rotation: Int): Boolean { 22 | return rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/freeform/FreeformViewAbs.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.freeform 2 | 3 | abstract class FreeformViewAbs(open val config: FreeformConfig) { 4 | abstract fun toScreenCenter() 5 | abstract fun moveToFirst() 6 | abstract fun destroy() 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/freeform/FreeformViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.freeform 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences.OnSharedPreferenceChangeListener 5 | import com.sunshine.freeform.app.MiFreeform 6 | 7 | class FreeformViewModel(context: Context) { 8 | 9 | private val sp = context.getSharedPreferences(MiFreeform.APP_SETTINGS_NAME, Context.MODE_PRIVATE) 10 | 11 | fun getBooleanSp(key: String, defaultValue: Boolean): Boolean { 12 | return sp.getBoolean(key, defaultValue) 13 | } 14 | 15 | fun getIntSp(key: String, defaultValue: Int): Int { 16 | return sp.getInt(key, defaultValue) 17 | } 18 | 19 | fun registerOnSharedPreferenceChangeListener(listener: OnSharedPreferenceChangeListener) { 20 | sp.registerOnSharedPreferenceChangeListener(listener) 21 | } 22 | 23 | fun unregisterOnSharedPreferenceChangeListener(listener: OnSharedPreferenceChangeListener) { 24 | sp.unregisterOnSharedPreferenceChangeListener(listener) 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/freeform/ScreenListener.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.freeform 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.content.IntentFilter 7 | 8 | /** 9 | * 创建一个监听器类 监听android锁屏与解锁事件 10 | */ 11 | class ScreenListener(private val mContext: Context) { 12 | private val mScreenReceiver: ScreenBroadcastReceiver 13 | private var mListeners: ArrayList = ArrayList() 14 | 15 | /** 16 | * screen状态广播接收者 17 | */ 18 | private inner class ScreenBroadcastReceiver : BroadcastReceiver() { 19 | private var action: String? = null 20 | override fun onReceive(context: Context, intent: Intent) { 21 | action = intent.action 22 | if (Intent.ACTION_SCREEN_ON == action) { // 开屏 23 | mListeners.forEach { 24 | it.onScreenOn() 25 | } 26 | } else if (Intent.ACTION_SCREEN_OFF == action) { // 锁屏 27 | mListeners.forEach { 28 | it.onScreenOff() 29 | } 30 | } else if (Intent.ACTION_USER_PRESENT == action) { // 解锁 31 | mListeners.forEach { 32 | it.onUserPresent() 33 | } 34 | } 35 | } 36 | } 37 | 38 | fun addScreenStateListener(listener: ScreenStateListener) { 39 | mListeners.add(listener) 40 | } 41 | 42 | fun removeScreenStateListener(listener: ScreenStateListener) { 43 | mListeners.remove(listener) 44 | } 45 | 46 | /** 47 | * 停止screen状态监听 48 | */ 49 | fun unregisterListener() { 50 | mContext.unregisterReceiver(mScreenReceiver) 51 | } 52 | 53 | interface ScreenStateListener { 54 | // 返回给调用者屏幕状态信息 55 | fun onScreenOn() 56 | fun onScreenOff() 57 | fun onUserPresent() 58 | } 59 | 60 | init { 61 | val filter = IntentFilter() 62 | filter.addAction(Intent.ACTION_SCREEN_ON) 63 | filter.addAction(Intent.ACTION_SCREEN_OFF) 64 | filter.addAction(Intent.ACTION_USER_PRESENT) 65 | 66 | mScreenReceiver = ScreenBroadcastReceiver() 67 | 68 | mContext.registerReceiver(mScreenReceiver, filter) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/freeform/StackSet.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.freeform 2 | 3 | import android.content.ComponentName 4 | 5 | /** 6 | * @author sunshine 7 | * @date 2021/3/18 8 | * 为了满足单一性栈,采用集合特性来实现栈 9 | */ 10 | class StackSet { 11 | 12 | private val elementData = ArrayList() 13 | 14 | /** 15 | * 将元素放到栈顶,但同时保证单一性 16 | * 时间复杂度O(n) 17 | */ 18 | fun push(element: FreeformViewAbs) { 19 | elementData.remove(element) 20 | elementData.add(element) 21 | } 22 | 23 | fun pop(): FreeformViewAbs { 24 | return elementData.removeAt(elementData.size - 1) 25 | } 26 | 27 | fun peek(): FreeformViewAbs { 28 | return elementData[elementData.size - 1] 29 | } 30 | 31 | fun remove(element: FreeformViewAbs) { 32 | elementData.remove(element) 33 | } 34 | 35 | fun clean() { 36 | elementData.forEach { 37 | it.destroy() 38 | remove(it) 39 | } 40 | } 41 | 42 | fun size(): Int { 43 | return elementData.size 44 | } 45 | 46 | fun get(index: Int): FreeformViewAbs { 47 | return elementData[index] 48 | } 49 | 50 | fun getByComponentName(componentName: ComponentName, userId: Int): FreeformViewAbs? { 51 | elementData.forEach { 52 | if (it.config.componentName!! == componentName && it.config.userId == userId) { 53 | return it 54 | } 55 | } 56 | return null 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/guide/FirstFragment.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.guide 2 | 3 | import android.os.Bundle 4 | import androidx.fragment.app.Fragment 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.view.animation.Animation 9 | import android.view.animation.AnimationUtils 10 | import androidx.navigation.fragment.findNavController 11 | import com.sunshine.freeform.R 12 | import com.sunshine.freeform.databinding.FragmentGuideFirstBinding 13 | import kotlinx.coroutines.Dispatchers 14 | import kotlinx.coroutines.GlobalScope 15 | import kotlinx.coroutines.launch 16 | 17 | class FirstFragment : Fragment() { 18 | private lateinit var binding: FragmentGuideFirstBinding 19 | 20 | override fun onCreateView( 21 | inflater: LayoutInflater, container: ViewGroup?, 22 | savedInstanceState: Bundle? 23 | ): View { 24 | binding = FragmentGuideFirstBinding.bind(inflater.inflate(R.layout.fragment_guide_first, container, false)) 25 | return binding.root 26 | } 27 | 28 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 29 | super.onViewCreated(view, savedInstanceState) 30 | 31 | binding.textViewHello.startAnimation(AnimationUtils.loadAnimation(requireContext(), R.anim.text_fade_in)) 32 | binding.textViewWelcome.startAnimation(AnimationUtils.loadAnimation(requireContext(), R.anim.text_fade_in)) 33 | 34 | GlobalScope.launch(Dispatchers.IO) { 35 | Thread.sleep(1000) 36 | launch(Dispatchers.Main) { 37 | binding.buttonNext.animate().alpha(1f).setDuration(750L).start() 38 | binding.buttonNext.setOnClickListener { 39 | findNavController().navigate(R.id.action_first_to_second) 40 | } 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/guide/GuideActivity.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.guide 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | import androidx.navigation.findNavController 6 | import androidx.navigation.ui.navigateUp 7 | import com.sunshine.freeform.R 8 | import com.sunshine.freeform.databinding.ActivityFreeformGuideBinding 9 | 10 | class GuideActivity : AppCompatActivity() { 11 | 12 | private lateinit var binding: ActivityFreeformGuideBinding 13 | 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | binding = ActivityFreeformGuideBinding.inflate(layoutInflater) 17 | setContentView(binding.root) 18 | 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/guide/GuideStudyActivity.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.guide 2 | 3 | import android.os.Build 4 | import androidx.appcompat.app.AppCompatActivity 5 | import android.os.Bundle 6 | import androidx.annotation.RequiresApi 7 | import com.sunshine.freeform.R 8 | import com.sunshine.freeform.databinding.ActivityGuideStudyBinding 9 | 10 | class GuideStudyActivity : AppCompatActivity() { 11 | private lateinit var binding: ActivityGuideStudyBinding 12 | 13 | override fun onCreate(savedInstanceState: Bundle?) { 14 | super.onCreate(savedInstanceState) 15 | binding = ActivityGuideStudyBinding.inflate(layoutInflater) 16 | setContentView(binding.root) 17 | 18 | callback = object : FreeformStudyViewNew.Callback { 19 | override fun onMoveClick() { 20 | binding.textViewGuideB.text = getString(R.string.guide_back) 21 | binding.textViewMiddle.alpha = 0f 22 | binding.textViewRight.alpha = 1f 23 | SecondFragment.callback?.onMoveClick() 24 | } 25 | 26 | override fun onCloseClick() { 27 | binding.textViewGuideB.text = getString(R.string.guide_to_full) 28 | SecondFragment.callback?.onCloseClick() 29 | } 30 | 31 | override fun onBackClick() { 32 | binding.textViewGuideB.text = getString(R.string.guide_close) 33 | binding.textViewRight.alpha = 0f 34 | binding.textViewLeft.alpha = 1f 35 | SecondFragment.callback?.onBackClick() 36 | } 37 | 38 | override fun onToBackStageClick() { 39 | binding.textViewGuideB.text = getString(R.string.guide_scale) 40 | binding.textViewRight.alpha = 0f 41 | SecondFragment.callback?.onToBackStageClick() 42 | } 43 | 44 | override fun onToFullClick() { 45 | binding.textViewGuideB.text = getString(R.string.guide_to_back_stage) 46 | binding.textViewRight.alpha = 1f 47 | binding.textViewLeft.alpha = 0f 48 | SecondFragment.callback?.onToFullClick() 49 | } 50 | 51 | override fun onScaleClick() { 52 | binding.textViewGuideB.text = getString(R.string.guide_success) 53 | SecondFragment.callback?.onScaleClick() 54 | } 55 | 56 | override fun onSuccess() { 57 | 58 | } 59 | 60 | } 61 | 62 | binding.textViewGuideB.text = getString(R.string.guide_move) 63 | binding.textViewMiddle.alpha = 1f 64 | SecondFragment.callback?.onSuccess() 65 | } 66 | 67 | companion object { 68 | var callback: FreeformStudyViewNew.Callback? = null 69 | } 70 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/main/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.main 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.core.view.WindowCompat 6 | import androidx.navigation.findNavController 7 | import androidx.navigation.ui.AppBarConfiguration 8 | import androidx.navigation.ui.navigateUp 9 | import androidx.fragment.app.Fragment 10 | import androidx.viewpager2.adapter.FragmentStateAdapter 11 | import androidx.viewpager2.widget.ViewPager2 12 | import com.sunshine.freeform.R 13 | import com.sunshine.freeform.databinding.ActivityMainBinding 14 | import kotlinx.android.synthetic.main.activity_main.* 15 | import java.util.* 16 | 17 | class MainActivity : AppCompatActivity() { 18 | 19 | private lateinit var appBarConfiguration: AppBarConfiguration 20 | private lateinit var binding: ActivityMainBinding 21 | 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | WindowCompat.setDecorFitsSystemWindows(window, false) 24 | super.onCreate(savedInstanceState) 25 | 26 | binding = ActivityMainBinding.inflate(layoutInflater) 27 | setContentView(binding.root) 28 | 29 | setSupportActionBar(binding.toolbar) 30 | 31 | binding.viewPager.apply { 32 | adapter = object : FragmentStateAdapter(this@MainActivity) { 33 | override fun getItemCount(): Int { 34 | return 2 35 | } 36 | 37 | override fun createFragment(position: Int): Fragment { 38 | return when(position) { 39 | 0 -> HomeFragment() 40 | else -> SettingFragment() 41 | } 42 | } 43 | } 44 | registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { 45 | override fun onPageSelected(position: Int) { 46 | super.onPageSelected(position) 47 | binding.navView.menu.getItem(position).isChecked = true 48 | } 49 | }) 50 | isUserInputEnabled = false 51 | offscreenPageLimit = 2 52 | } 53 | navView.apply { 54 | setOnItemSelectedListener { 55 | when (it.itemId) { 56 | R.id.navigation_home -> { 57 | binding.viewPager.currentItem = 0 58 | } 59 | else -> { 60 | binding.viewPager.currentItem = 1 61 | } 62 | } 63 | true 64 | } 65 | } 66 | } 67 | 68 | override fun onSupportNavigateUp(): Boolean { 69 | val navController = findNavController(R.id.nav_host_fragment_content_main) 70 | return navController.navigateUp(appBarConfiguration) 71 | || super.onSupportNavigateUp() 72 | } 73 | 74 | fun changeToSetting() { 75 | binding.viewPager.currentItem = 1 76 | } 77 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/splash/SplashViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.splash 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import androidx.lifecycle.AndroidViewModel 6 | import com.sunshine.freeform.app.MiFreeform 7 | 8 | class SplashViewModel(application: Application) : AndroidViewModel(application) { 9 | private val sp = application.getSharedPreferences(MiFreeform.APP_SETTINGS_NAME, Context.MODE_PRIVATE) 10 | 11 | fun getBooleanSp(key: String, default: Boolean): Boolean { 12 | return sp.getBoolean(key, default) 13 | } 14 | 15 | fun getIntSp(key: String, default: Int): Int { 16 | return sp.getInt(key, default) 17 | } 18 | 19 | fun putIntSp(key: String, value: Int) { 20 | sp.edit().putInt(key, value).apply() 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/view/CircleImageView.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.view 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Outline 6 | import android.graphics.Path 7 | import android.graphics.RectF 8 | import android.util.AttributeSet 9 | import android.view.View 10 | import android.view.ViewOutlineProvider 11 | import androidx.appcompat.widget.AppCompatImageView 12 | import com.sunshine.freeform.R 13 | 14 | /** 15 | * @Author : jiyajie 16 | * @Time : On 2021/11/23 09:45 17 | * @Description : CircleImageViewV2 18 | */ 19 | class CircleImageView @JvmOverloads constructor( 20 | context: Context, attrs: AttributeSet? = null 21 | ) : AppCompatImageView(context, attrs) { 22 | 23 | 24 | private var mRadius: Int = 0 25 | private val viewOutlineProvider: ViewOutlineProvider by lazy { 26 | object : ViewOutlineProvider() { 27 | override fun getOutline(view: View?, outline: Outline?) { 28 | val width = width 29 | val height = height 30 | outline?.setRoundRect(0, 0, width, height, mRadius.toFloat()) 31 | } 32 | 33 | } 34 | } 35 | private var path: Path? 36 | private var rect: RectF? 37 | 38 | 39 | init { 40 | val obtainStyledAttributes = 41 | context.obtainStyledAttributes(attrs, androidx.appcompat.R.styleable.AppCompatImageView) 42 | obtainStyledAttributes.let { 43 | mRadius = 10 44 | it.recycle() 45 | path = Path() 46 | rect = RectF() 47 | setRound(mRadius) 48 | } 49 | } 50 | 51 | //设置圆角图片 52 | private fun setRound(radius: Int) = apply { 53 | val isChange = radius != mRadius 54 | mRadius = radius 55 | if (mRadius != 0) { 56 | outlineProvider = viewOutlineProvider 57 | clipToOutline = true 58 | val width = width.toFloat() 59 | val height = height.toFloat() 60 | rect?.set(0f, 0f, width, height) 61 | path?.reset() 62 | rect?.let { path?.addRoundRect(it,mRadius.toFloat(),mRadius.toFloat(),Path.Direction.CW) } 63 | } else { 64 | clipToOutline = false 65 | } 66 | 67 | if (isChange) { 68 | invalidateOutline() 69 | } 70 | 71 | } 72 | 73 | override fun draw(canvas: Canvas){ 74 | var clip = false 75 | if (mRadius > 0) { 76 | clip = true 77 | canvas?.save() 78 | path?.let { canvas?.clipPath(it) } 79 | 80 | } 81 | super.draw(canvas!!) 82 | if (clip) { 83 | canvas?.restore() 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/view/FreeformRootViewGroup.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.util.Log 6 | import android.view.KeyEvent 7 | import androidx.constraintlayout.widget.ConstraintLayout 8 | 9 | //TODO 10 | class FreeformRootViewGroup @JvmOverloads constructor( 11 | context: Context, attrs: AttributeSet? = null 12 | ) : ConstraintLayout(context, attrs) { 13 | 14 | override fun dispatchKeyEvent(event: KeyEvent): Boolean { 15 | if (event.keyCode == KeyEvent.KEYCODE_BACK) { 16 | Log.e("根目录", "返回") 17 | } 18 | return super.dispatchKeyEvent(event) 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/ui/view/MTextView.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.ui.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | 6 | /** 7 | * @date 2022/8/23 8 | * @author sunshine0523 9 | * 获得焦点以自动跑马灯 10 | */ 11 | class MTextView @JvmOverloads constructor( 12 | context: Context, attrs: AttributeSet? = null 13 | ) : androidx.appcompat.widget.AppCompatTextView(context, attrs) { 14 | 15 | override fun isFocused(): Boolean { 16 | return true 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/utils/PackageUtils.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.utils 2 | 3 | import android.content.pm.LauncherApps 4 | import android.content.pm.PackageManager 5 | import android.os.Build 6 | import android.os.UserHandle 7 | import androidx.annotation.RequiresApi 8 | 9 | 10 | /** 11 | * @author sunshine 12 | * @date 2021/3/22 13 | * 包相关辅助函数 14 | */ 15 | object PackageUtils { 16 | 17 | //判断是否安装了这个应用 18 | fun hasInstallThisPackage(packageName: String, packageManager: PackageManager): Boolean { 19 | return try { 20 | packageManager.getApplicationInfo(packageName, 0) 21 | true 22 | } catch (e: Exception) { 23 | false 24 | } 25 | } 26 | 27 | //判断某个用户是否安装了这个应用 28 | fun hasInstallThisPackageWithUserId( 29 | packageName: String, 30 | launcherApps: LauncherApps, 31 | userHandle: UserHandle 32 | ): Boolean { 33 | return try { 34 | launcherApps.getApplicationInfo(packageName, 0, userHandle) 35 | true 36 | } catch (e: Exception) { 37 | false 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/utils/PermissionUtils.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.utils 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.pm.PackageManager 6 | import android.os.Build 7 | import android.provider.Settings 8 | import android.provider.Settings.SettingNotFoundException 9 | import android.text.TextUtils.SimpleStringSplitter 10 | import androidx.core.app.ActivityCompat 11 | 12 | /** 13 | * @date 2022/8/26 14 | * @author sunshine0523 15 | */ 16 | object PermissionUtils { 17 | 18 | fun checkNotificationListenerPermission(context: Context): Boolean { 19 | var enable = false 20 | val flat = Settings.Secure.getString(context.contentResolver, "enabled_notification_listeners") 21 | if (flat != null) { 22 | enable = flat.contains(context.packageName) 23 | } 24 | return enable 25 | } 26 | 27 | fun checkOverlayPermission(context: Context): Boolean { 28 | return Settings.canDrawOverlays(context) 29 | } 30 | 31 | fun isAccessibilitySettingsOn(context: Context): Boolean { 32 | var accessibilityEnabled = 0 33 | val service = context.packageName + "/com.sunshine.freeform.service.KeepAliveService" 34 | try { 35 | accessibilityEnabled = Settings.Secure.getInt( 36 | context.applicationContext.contentResolver, 37 | Settings.Secure.ACCESSIBILITY_ENABLED 38 | ) 39 | } catch (e: SettingNotFoundException) { 40 | } 41 | val mStringColonSplitter = SimpleStringSplitter(':') 42 | if (accessibilityEnabled == 1) { 43 | val settingValue = Settings.Secure.getString( 44 | context.applicationContext.contentResolver, 45 | Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES 46 | ) 47 | if (settingValue != null) { 48 | mStringColonSplitter.setString(settingValue) 49 | while (mStringColonSplitter.hasNext()) { 50 | val accessibilityService = mStringColonSplitter.next() 51 | if (accessibilityService.equals(service, ignoreCase = true)) { 52 | return true 53 | } 54 | } 55 | } 56 | } 57 | return false 58 | } 59 | 60 | fun checkPostNotificationPermission(activity: Activity) { 61 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 62 | if (ActivityCompat.checkSelfPermission(activity, 63 | android.Manifest.permission.POST_NOTIFICATIONS 64 | ) == PackageManager.PERMISSION_DENIED) { 65 | ActivityCompat.requestPermissions(activity, 66 | listOf(android.Manifest.permission.POST_NOTIFICATIONS).toTypedArray(),100); 67 | } 68 | } 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /app/src/main/java/com/sunshine/freeform/utils/ServiceUtils.kt: -------------------------------------------------------------------------------- 1 | package com.sunshine.freeform.utils 2 | 3 | import android.app.ActivityManager 4 | import android.app.IActivityManager 5 | import android.app.IActivityTaskManager 6 | import android.content.Context 7 | import android.hardware.display.DisplayManager 8 | import android.hardware.input.IInputManager 9 | import android.view.IWindowManager 10 | import android.view.WindowManager 11 | import rikka.shizuku.ShizukuBinderWrapper 12 | import rikka.shizuku.SystemServiceHelper 13 | 14 | /** 15 | * @date 2021/2/1 16 | * 服务相关工具类 17 | */ 18 | object ServiceUtils { 19 | lateinit var activityManager: IActivityManager 20 | lateinit var activityTaskManager: IActivityTaskManager 21 | lateinit var displayManager: DisplayManager 22 | lateinit var windowManager: WindowManager 23 | lateinit var iWindowManager: IWindowManager 24 | lateinit var inputManager: IInputManager 25 | 26 | fun initWithShizuku(context: Context) { 27 | activityManager = IActivityManager.Stub.asInterface( 28 | ShizukuBinderWrapper( 29 | SystemServiceHelper.getSystemService("activity") 30 | ) 31 | ) 32 | activityTaskManager = IActivityTaskManager.Stub.asInterface( 33 | ShizukuBinderWrapper( 34 | SystemServiceHelper.getSystemService("activity_task") 35 | ) 36 | ) 37 | displayManager = context.getSystemService(DisplayManager::class.java) 38 | windowManager = context.getSystemService(WindowManager::class.java) 39 | iWindowManager = IWindowManager.Stub.asInterface( 40 | ShizukuBinderWrapper( 41 | SystemServiceHelper.getSystemService("window") 42 | ) 43 | ) 44 | inputManager = IInputManager.Stub.asInterface( 45 | ShizukuBinderWrapper( 46 | SystemServiceHelper.getSystemService("input") 47 | ) 48 | ) 49 | } 50 | 51 | /** 52 | * 判断某个服务是否正在运行的方法 53 | * 54 | * @param mContext 55 | * @param serviceName 是包名+服务的类名(例如:net.loonggg.testbackstage.TestService) 56 | * @return true代表正在运行,false代表服务没有正在运行 57 | */ 58 | fun isServiceWork(mContext: Context, serviceName: String): Boolean { 59 | var isWork = false 60 | val myAM = mContext 61 | .getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager 62 | val myList: List = myAM.getRunningServices(40) 63 | if (myList.isEmpty()) { 64 | return false 65 | } 66 | for (i in myList.indices) { 67 | val mName: String = myList[i].service.className 68 | myList[i].service.className 69 | if (mName == serviceName) { 70 | isWork = true 71 | break 72 | } 73 | } 74 | return isWork 75 | } 76 | } -------------------------------------------------------------------------------- /app/src/main/res/anim/hang_up_tip_fade_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/anim/hang_up_tip_fade_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/anim/text_fade_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-night/color_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-night/ic_add.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-night/ic_alipay.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-night/ic_all.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-night/ic_paypal.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-night/ic_wechat_pay.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bar_corners_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bar_corners_bg_flyme.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/color_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/floating_button_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/floating_button_bg_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/hang_up_tip_circle_left_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 17 | 18 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/hang_up_tip_circle_left_up.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 17 | 18 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/hang_up_tip_circle_right_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 17 | 18 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/hang_up_tip_circle_right_up.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 17 | 18 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_alipay.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_all.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_api.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_forward.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_back.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_coolapk.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_done.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_error_white.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_github.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_guide.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_money.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_paypal.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_qq.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_question.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_star.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_telegram.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_wechat_pay.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tile_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Live-Block/Flyme-FreeForm/6283625c126efd072cc69b92321bf2b771a83f85/app/src/main/res/drawable/tile_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/view_corners_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_choose_apps.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 23 | 24 | 25 | 26 | 37 | 38 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_floating.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_floating_apps_sort.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 23 | 24 | 25 | 26 | 34 | 35 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_freeform_guide.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 18 | 19 | 25 | 26 | 27 | 28 | 37 | 38 | 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_permission.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 20 | 21 | 22 | 23 | 26 | 27 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_recent.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_guide_first.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 18 | 19 | 30 | 31 | 37 | 38 | 50 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_guide_second.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 26 | 27 |