├── .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 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/appInsightsSettings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
25 |
26 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
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 |
37 |
38 |
51 |
52 |
62 |
63 |
73 |
74 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_app.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
21 |
22 |
38 |
39 |
48 |
49 |
58 |
59 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_app2.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
25 |
26 |
35 |
36 |
37 |
48 |
49 |
57 |
58 |
59 |
60 |
70 |
71 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_floating.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
16 |
17 |
32 |
33 |
42 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_recent.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/task_view_menu_option.xml:
--------------------------------------------------------------------------------
1 |
15 |
16 |
22 |
23 |
30 |
31 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_all_apps.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
21 |
22 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_bar.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
20 |
24 |
25 |
26 |
36 |
37 |
41 |
42 |
43 |
53 |
54 |
58 |
59 |
60 |
66 |
67 |
73 |
74 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_bar_flyme.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
20 |
21 |
25 |
26 |
27 |
38 |
39 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_choose_app_floating.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
18 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_choose_app_floting_view_recycler_app.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
12 |
13 |
18 |
19 |
29 |
30 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_edit.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_floating_button.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
18 |
19 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_floating_button_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_floating_button_right.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_hang_up_tip.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
20 |
21 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/bottom_nav_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_choose_apps.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/wechat.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Live-Block/Flyme-FreeForm/6283625c126efd072cc69b92321bf2b771a83f85/app/src/main/res/mipmap-xxhdpi/wechat.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Live-Block/Flyme-FreeForm/6283625c126efd072cc69b92321bf2b771a83f85/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/navigation/nav_graph.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
21 |
22 |
23 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/navigation/nav_guide.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
20 |
21 |
22 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FF000000
4 | #FFFFFFFF
5 | @color/google_grey_900
6 | @color/white
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rCN/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - 屏幕左侧
5 | - 屏幕右侧
6 |
7 |
8 |
9 | - 使用无障碍
10 | - 使用前台服务
11 |
12 |
13 |
14 | - A
15 | - B
16 | - C
17 | - D
18 | - E
19 | - F
20 | - G
21 | - H
22 | - I
23 | - J
24 | - K
25 | - L
26 | - M
27 | - N
28 | - O
29 | - P
30 | - Q
31 | - R
32 | - S
33 | - T
34 | - U
35 | - V
36 | - W
37 | - X
38 | - Y
39 | - Z
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - android
5 | - com.android.systemui
6 | - com.motorola.launcher3
7 | - com.android.launcher3
8 | - com.google.android.apps.nexuslauncher
9 |
10 |
11 |
12 | - Left side of screen
13 | - Right side of screen
14 |
15 |
16 |
17 | - -1
18 | - 1
19 |
20 |
21 |
22 | - Accessibility service
23 | - Foreground service
24 |
25 |
26 |
27 | - 0
28 | - 1
29 |
30 |
31 |
32 | - A
33 | - B
34 | - C
35 | - D
36 | - E
37 | - F
38 | - G
39 | - H
40 | - I
41 | - J
42 | - K
43 | - L
44 | - M
45 | - N
46 | - O
47 | - P
48 | - Q
49 | - R
50 | - S
51 | - T
52 | - U
53 | - V
54 | - W
55 | - X
56 | - Y
57 | - Z
58 |
59 |
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FF000000
4 | #FFFFFFFF
5 | #FFCCCCCC
6 |
7 | #1A73E8
8 | #8AB4F8
9 | #202124
10 |
11 | @color/google_blue_600
12 | @color/google_blue_300
13 | @color/color_primary_light
14 |
15 | @color/white
16 | @color/black
17 |
18 | #ffFFC107
19 | #FF4CAF50
20 |
21 | #FFA9CAEB
22 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 16dp
3 |
4 | 32dp
5 | 0dp
6 | 24dp
7 | 36dp
8 | 18dp
9 | 28dp
10 | 96dp
11 |
12 | 16dp
13 | 16dp
14 | 64dp
15 | 16sp
16 | 24dp
17 | 12dp
18 |
19 | 50dp
20 | 70dp
21 | 10sp
22 | 24dp
23 | 80dp
24 | 32dp
25 | 16dp
26 | 16dp
27 | 24dp
28 | 16sp
29 | 75dp
30 |
31 | 10sp
32 | 32sp
33 | 12sp
34 | 20sp
35 | 20dp
36 | 24dp
37 |
38 | 32dp
39 | 128dp
40 | 24dp
41 | 16dp
42 | 16dp
43 | 4dp
44 | 8dp
45 | 64dp
46 | 64dp
47 |
48 | 72dp
49 | 24dp
50 |
51 |
52 | 16dp
53 | 18dp
54 | 16dp
55 | 4dp
56 |
57 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
19 |
20 |
25 |
26 |
27 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/accessibility_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/test/java/com/sunshine/freeform/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.sunshine.freeform
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/app/update.md:
--------------------------------------------------------------------------------
1 | # 2.5.7 beta
2 | - 仍然没有排查出黑边问题。如果你有问题,请尽量把米窗设置界面截图、出现问题应用告诉我
3 | - 修复 在有正在运行程序的情况下锁屏,米窗崩溃的问题 q220902.1
4 | - 修复(待测试) 某些竖屏软件也会横屏的问题 q220902.2
5 | - 修复(待测试) 尝试修复侧边栏在屏幕中间的问题 q220902.3
6 | - 新增 通过活动打开侧边栏
7 |
8 | # 2.5.8 beta
9 | 特别提醒:米窗为类原生制作,在任何定制系统上运行都有可能产生不可预知的问题
10 | 特别提醒:米窗为类原生制作,在任何定制系统上运行都有可能产生不可预知的问题
11 | 特别提醒:米窗为类原生制作,在任何定制系统上运行都有可能产生不可预知的问题
12 | - 优化 横屏小窗挂起的位置 q220903.1
13 | - 移除了兼容性模式选项
14 | - 在Android 10设备上重新支持xp阻止跳出小窗
15 | - 修复 在Android 10设备上小窗内屏幕旋转失效的问题 q220903.2
16 |
17 | # 2.5.9 beta
18 | - 修复 尝试修复记录的小窗大小异常的问题 q220904.1
19 | - 优化 小窗缩放允许的范围更大
20 | - 修复 调整dpi后导致负数dpi从而米窗崩溃的问题 q220904.2
21 | - 优化 小窗内部旋转时,如果旋转后的尺寸大于屏幕尺寸,则进行调整 q220904.3
22 | - 修复 挂起后再恢复小窗,小窗缩放异常的问题 q220904.5
23 | - 优化 尝试更加跟手的缩放操作 q220904.6
24 | - 优化 更小的底栏
25 | - 新增 手动调整小窗方向 q220904.7
26 | - 优化 尝试更加流畅的缩放动画 q220904.8
27 |
28 | # 2.5.10
29 | - 新增 前台服务保活
30 |
31 | # 2.5.12
32 | - 优化 支持多点触控
33 |
34 | # 2.5.13
35 | - 新增 支持从多任务界面打开小窗
36 | - 优化 界面优化
37 | - 修复 使用活动打开小窗选择界面时,可能产生无法关闭选择界面的情况
38 | - 修复 部分设备上屏幕旋转引起的分辨率错误问题
39 | - 修复 在部分情况(如在多任务界面切换屏幕方向)而导致无法识别屏幕旋转的问题
40 |
41 | # 2.5.14
42 | - 优化 一些界面优化
43 | - 优化 如果小窗移动到屏幕外导致无法控制,可以尝试从侧边栏再次点击该应用以移动到屏幕中心 q220917.4
44 | - 修复 屏幕旋转后QQ的缩放不正确的问题 q220910.1
45 | - 优化 QQ和微信也支持缩放了 q220917.1
46 | - 移除了引导界面,但是仍然可以自行查看 q220917.2
47 | - 优化 锁屏后小窗的状态 q220917.3
48 |
49 | # 2.5.15
50 | - 新增 (可能)对Android 8-9进行支持
51 | - 新增 (测试)自由调节小窗比例
52 | - 新增 侧边栏透明度支持调整。侧边栏还会进行优化,敬请期待
53 |
54 | # 2.5.16
55 | - 修复 小窗挂起后比例有误的问题
56 | - 修复 小窗在自由调整比例的情况下小窗内容比例可能不刷新的问题
57 |
58 | # 2.5.17
59 | - 修复 开启记录位置选项后触控错位的问题 q220925.3
60 |
61 | # 2.5.18
62 | - 修复 屏幕旋转后侧边栏不贴边的问题 q221208.1
63 | - 优化 多任务打开米窗的方式更改为点击应用图标后发现 issue#7
64 |
65 | # 2.5.19
66 | - 优化 多任务打开米窗的样式 by duzhaokun123
67 |
68 | ## 探究
69 | - 如果应用发生了crash,那么onConfigurationChanged是不会调用的,configuration都是不变化的
70 | - 将触摸设置为一等公民,以支持多点触控,也可以看一下为什么那样,多点触控就不支持了... q220906.1
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | dependencies {
4 | classpath 'com.github.megatronking.stringfog:gradle-plugin:3.0.0'
5 | // 选用加解密算法库,默认实现了xor算法,也可以使用自己的加解密库。
6 | classpath 'com.github.megatronking.stringfog:xor:3.0.0'
7 | classpath "dev.rikka.tools.refine:gradle-plugin:4.1.0"
8 | }
9 | }
10 |
11 | plugins {
12 | id 'com.android.application' version '7.2.2' apply false
13 | id 'com.android.library' version '7.2.2' apply false
14 | id 'org.jetbrains.kotlin.android' version '1.7.22' apply false
15 | }
16 |
17 | task clean(type: Delete) {
18 | delete rootProject.buildDir
19 | }
20 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Live-Block/Flyme-FreeForm/6283625c126efd072cc69b92321bf2b771a83f85/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Aug 15 14:40:16 CST 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/hidden-api/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/hidden-api/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | }
4 |
5 | android {
6 | namespace 'com.freeform.hiddenapi'
7 | compileSdk 33
8 |
9 | defaultConfig {
10 | minSdk 28
11 | targetSdk 33
12 | }
13 |
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | compileOptions {
21 | sourceCompatibility JavaVersion.VERSION_1_8
22 | targetCompatibility JavaVersion.VERSION_1_8
23 | }
24 | }
25 |
26 | dependencies {
27 | compileOnly 'androidx.annotation:annotation:1.6.0'
28 | annotationProcessor "dev.rikka.tools.refine:annotation-processor:4.1.0"
29 | compileOnly "dev.rikka.tools.refine:annotation:4.1.0"
30 | }
--------------------------------------------------------------------------------
/hidden-api/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
--------------------------------------------------------------------------------
/hidden-api/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/hidden-api/src/main/aidl/android/app/ActivityManager.aidl:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2014, The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package android.app;
18 |
19 | parcelable ActivityManager.RunningTaskInfo;
20 |
--------------------------------------------------------------------------------
/hidden-api/src/main/aidl/android/app/IApplicationThread.aidl:
--------------------------------------------------------------------------------
1 | package android.app;
2 |
3 | interface IApplicationThread {
4 | }
5 |
--------------------------------------------------------------------------------
/hidden-api/src/main/aidl/android/app/IServiceConnection.aidl:
--------------------------------------------------------------------------------
1 | package android.app;
2 |
3 | interface IServiceConnection {
4 |
5 | void connected(in ComponentName name, IBinder service, boolean dead);
6 | }
7 |
--------------------------------------------------------------------------------
/hidden-api/src/main/aidl/android/app/ITaskStackListener.aidl:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2014, The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package android.app;
18 |
19 | import android.app.ActivityManager;
20 |
21 | interface ITaskStackListener {
22 | /**
23 | * Called when a task is added.
24 | *
25 | * @param taskId id of the task.
26 | * @param componentName of the activity that the task is being started with.
27 | */
28 | void onTaskCreated(int taskId, in ComponentName componentName);
29 |
30 | /**
31 | * Called when the task is about to be finished but before its surfaces are
32 | * removed from the window manager. This allows interested parties to
33 | * perform relevant animations before the window disappears.
34 | *
35 | * @param taskInfo info about the task being removed
36 | */
37 | void onTaskRemovalStarted(in ActivityManager.RunningTaskInfo taskInfo);
38 |
39 | /**
40 | * Called when a task is reparented to a stack on a different display.
41 | *
42 | * @param taskId id of the task which was moved to a different display.
43 | * @param newDisplayId id of the new display.
44 | */
45 | void onTaskDisplayChanged(int taskId, int newDisplayId);
46 |
47 | /**
48 | * Called when a task is moved to the front of its stack.
49 | *
50 | * @param taskInfo info about the task which moved
51 | */
52 | void onTaskMovedToFront(in ActivityManager.RunningTaskInfo taskInfo);
53 |
54 | /**
55 | * Called when a task changes its requested orientation. It is different from {@link
56 | * #onActivityRequestedOrientationChanged(int, int)} in the sense that this method is called
57 | * when a task changes requested orientation due to activity launch, dimiss or reparenting.
58 | *
59 | * @param taskId id of the task.
60 | * @param requestedOrientation the new requested orientation of this task as screen orientations
61 | * in {@link android.content.pm.ActivityInfo}.
62 | * only support 12+
63 | */
64 | void onTaskRequestedOrientationChanged(int taskId, int requestedOrientation);
65 |
66 | /**
67 | * Called when a activity’s orientation is changed due to it calling
68 | * ActivityManagerService.setRequestedOrientation
69 | *
70 | * @param taskId id of the task that the activity is in.
71 | * @param requestedOrientation the new requested orientation.
72 | * only support 11-
73 | */
74 | void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation);
75 |
76 | /**
77 | * Called when a rotation is about to start on the foreground activity.
78 | * This applies for:
79 | * * free sensor rotation
80 | * * forced rotation
81 | * * rotation settings set through adb command line
82 | * * rotation that occurs when rotation tile is toggled in quick settings
83 | *
84 | * @param displayId id of the display where activity will rotate
85 | */
86 | void onActivityRotation(int displayId);
87 | }
--------------------------------------------------------------------------------
/hidden-api/src/main/aidl/android/content/IIntentReceiver.aidl:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 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.content;
18 |
19 | interface IIntentReceiver {
20 |
21 | }
--------------------------------------------------------------------------------
/hidden-api/src/main/aidl/android/content/IIntentSender.aidl:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 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.content;
18 |
19 | interface IIntentSender {
20 |
21 | }
--------------------------------------------------------------------------------
/hidden-api/src/main/java/android/app/ActivityOptionsHidden.java:
--------------------------------------------------------------------------------
1 | package android.app;
2 |
3 | import android.os.Bundle;
4 |
5 | import dev.rikka.tools.refine.RefineAs;
6 |
7 | @RefineAs(ActivityOptions.class)
8 | public class ActivityOptionsHidden {
9 | public int getCallerDisplayId() {
10 | throw new RuntimeException("Stub!");
11 | }
12 |
13 | public ActivityOptions setCallerDisplayId(int callerDisplayId) {
14 | throw new RuntimeException("Stub!");
15 | }
16 |
17 | public Bundle toBundle() {
18 | throw new RuntimeException("Stub!");
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/hidden-api/src/main/java/android/app/IActivityManager.java:
--------------------------------------------------------------------------------
1 | package android.app;
2 |
3 | import android.content.Intent;
4 | import android.content.IIntentSender;
5 | import android.content.IIntentReceiver;
6 | import android.os.Binder;
7 | import android.os.Bundle;
8 | import android.os.IBinder;
9 | import android.os.IInterface;
10 |
11 | import java.util.List;
12 |
13 | public interface IActivityManager extends IInterface {
14 | abstract class Stub extends Binder implements IServiceConnection {
15 | public static IActivityManager asInterface(IBinder obj)
16 | {
17 | throw new RuntimeException("Stub!");
18 | }
19 | }
20 |
21 | void forceStopPackage(String packageName, int userId);
22 | List getTasks(int maxNum);
23 | Intent getIntentForIntentSender(IIntentSender sender);
24 |
25 | int startActivityAsUserWithFeature(
26 | IApplicationThread caller,
27 | String callingPackage,
28 | String callingFeatureId,
29 | Intent intent,
30 | String resolvedType,
31 | IBinder resultTo,
32 | String resultWho,
33 | int requestCode,
34 | int flags,
35 | ProfilerInfo profilerInfo,
36 | Bundle options,
37 | int userId);
38 |
39 | int sendIntentSender(
40 | IIntentSender target,
41 | IBinder whitelistToken,
42 | int code,
43 | Intent intent,
44 | String resolvedType,
45 | IIntentReceiver finishedReceiver,
46 | String requiredPermission,
47 | Bundle options);
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/hidden-api/src/main/java/android/app/IActivityTaskManager.java:
--------------------------------------------------------------------------------
1 | package android.app;
2 |
3 | import android.os.Binder;
4 | import android.os.IBinder;
5 | import android.os.IInterface;
6 |
7 | public interface IActivityTaskManager extends IInterface {
8 | abstract class Stub extends Binder implements IServiceConnection {
9 | public static IActivityTaskManager asInterface(IBinder obj)
10 | {
11 | throw new RuntimeException("Stub!");
12 | }
13 | }
14 |
15 | void moveRootTaskToDisplay(int taskId, int displayId);
16 |
17 | void moveStackToDisplay(int stackId, int displayId);
18 |
19 | void registerTaskStackListener(ITaskStackListener iTaskStackListener);
20 |
21 | void unregisterTaskStackListener(ITaskStackListener iTaskStackListener);
22 | }
23 |
--------------------------------------------------------------------------------
/hidden-api/src/main/java/android/app/PendingIntentHidden.java:
--------------------------------------------------------------------------------
1 | package android.app;
2 |
3 | import android.content.Context;
4 | import android.content.IIntentSender;
5 | import android.content.Intent;
6 | import android.os.Bundle;
7 | import android.os.Handler;
8 | import android.os.IBinder;
9 |
10 | import androidx.annotation.Nullable;
11 |
12 | import dev.rikka.tools.refine.RefineAs;
13 |
14 | @RefineAs(PendingIntent.class)
15 | public class PendingIntentHidden {
16 | public int sendAndReturnResult(
17 | Context context, int code, @Nullable Intent intent,
18 | @Nullable PendingIntent.OnFinished onFinished, @Nullable Handler handler,
19 | @Nullable String requiredPermission, @Nullable Bundle options) {
20 | throw new RuntimeException("Stub!");
21 | }
22 |
23 | public IIntentSender getTarget() {
24 | throw new RuntimeException("Stub!");
25 | }
26 |
27 | public IBinder getWhitelistToken() {
28 | throw new RuntimeException("Stub!");
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/hidden-api/src/main/java/android/app/ProfilerInfo.java:
--------------------------------------------------------------------------------
1 | package android.app;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 |
6 | public class ProfilerInfo implements Parcelable {
7 |
8 | protected ProfilerInfo(Parcel in) {
9 | }
10 |
11 | public static final Creator CREATOR = new Creator() {
12 | @Override
13 | public ProfilerInfo createFromParcel(Parcel in) {
14 | return new ProfilerInfo(in);
15 | }
16 |
17 | @Override
18 | public ProfilerInfo[] newArray(int size) {
19 | return new ProfilerInfo[size];
20 | }
21 | };
22 |
23 | @Override
24 | public int describeContents() {
25 | throw new RuntimeException("Stub!");
26 | }
27 |
28 | @Override
29 | public void writeToParcel(Parcel parcel, int i) {
30 | throw new RuntimeException("Stub!");
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/hidden-api/src/main/java/android/app/TaskStackListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 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.app;
18 |
19 | import android.content.ComponentName;
20 | import android.os.RemoteException;
21 |
22 | public class TaskStackListener extends ITaskStackListener.Stub{
23 | @Override
24 | public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException {
25 |
26 | }
27 |
28 | @Override
29 | public void onTaskRemovalStarted(ActivityManager.RunningTaskInfo taskInfo) throws RemoteException {
30 |
31 | }
32 |
33 | @Override
34 | public void onTaskDisplayChanged(int taskId, int newDisplayId) throws RemoteException {
35 |
36 | }
37 |
38 | @Override
39 | public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) throws RemoteException {
40 |
41 | }
42 |
43 | @Override
44 | public void onTaskRequestedOrientationChanged(int taskId, int requestedOrientation) throws RemoteException {
45 |
46 | }
47 |
48 | @Override
49 | public void onActivityRotation(int displayId) throws RemoteException {
50 |
51 | }
52 |
53 | @Override
54 | public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) throws RemoteException {
55 |
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/hidden-api/src/main/java/android/content/ContextHidden.java:
--------------------------------------------------------------------------------
1 | package android.content;
2 |
3 | import android.os.UserHandle;
4 |
5 | import dev.rikka.tools.refine.RefineAs;
6 |
7 | @RefineAs(Context.class)
8 | public class ContextHidden {
9 |
10 | public UserHandle getUser() {
11 | throw new RuntimeException("Stub!");
12 | }
13 |
14 | public int getUserId() {
15 | throw new RuntimeException("Stub!");
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/open_api.md:
--------------------------------------------------------------------------------
1 | # 米窗开放API(持续更新)
2 |
3 | 米窗调用接口现已对外开放,你可以选择以下方式启动米窗提供的小窗:
4 |
5 | ## 1.打开米窗提供的应用选择界面
6 | 米窗提供的打开应用选择界面的活动为:com.sunshine.freeform.ui.floating.FloatingActivity。你可以通过其他应用调用该活动以打开应用选择界面。
7 |
8 | 请注意:该活动目前需要米窗的保活服务处于运行状态
9 |
10 | ## 2.直接打开米窗提供的小窗界面
11 | 除上述方式外,米窗还提供广播方式接收外部应用发送的打开小窗指令。具体例子如下:
12 |
13 | ```kotlin
14 | ### 方法1:
15 | val packageName: String = "com.sunshine.freeform"
16 | val activityName: String = "com.sunshine.freeform.ui.main.MainActivity"
17 | val userId: Int = 0
18 | val intent = Intent("com.sunshine.freeform.start_freeform").apply {
19 | setPackage("com.sunshine.freeform")
20 | //要启动小窗程序的包名:如com.sunshine.freeform
21 | putExtra("packageName", packageName)
22 | //要启动小窗的活动名称,请注意,该活动可能需要对外暴露才可启动。如com.sunshine.freeform.ui.main.MainActivity
23 | putExtra("activityName", activityName)
24 | //可选,默认为-1。对于系统存在“应用分身”等情况,可以指定userId
25 | putExtra("userId", userId)
26 | //
27 | putExtra(Intent.EXTRA_INTENT, intent)
28 | }
29 | context.sendBroadcast(intent)
30 | ### 方法2:
31 | val packageName: String = "com.sunshine.freeform"
32 | val activityName: String = "com.sunshine.freeform.ui.main.MainActivity"
33 | val startIntent: Intent = Intent().setComponent(ComponentName(packageName, activityName)
34 | val intent = Intent("com.sunshine.freeform.start_freeform").apply {
35 | setPackage("com.sunshine.freeform")
36 | //
37 | putExtra(Intent.EXTRA_INTENT, startIntent)
38 | }
39 | context.sendBroadcast(intent)
40 |
41 | ```
--------------------------------------------------------------------------------
/open_api_zh-Hans.md:
--------------------------------------------------------------------------------
1 | # 米窗开放API(持续更新)
2 |
3 | [English](https://github.com/sunshine0523/Mi-FreeForm/blob/master/open_api.md)
4 |
5 | 米窗调用接口现已对外开放,你可以选择以下方式启动米窗提供的小窗:
6 |
7 | ## 1.打开米窗提供的应用选择界面
8 | 米窗提供的打开应用选择界面的活动为:com.sunshine.freeform.ui.floating.FloatingActivity。你可以通过其他应用调用该活动以打开应用选择界面。
9 |
10 | 请注意:该活动目前需要米窗的保活服务处于运行状态
11 |
12 | ## 2.直接打开米窗提供的小窗界面
13 | 除上述方式外,米窗还提供广播方式接收外部应用发送的打开小窗指令。具体例子如下:
14 |
15 | ```kotlin
16 | val packageName: String = "com.sunshine.freeform"
17 | val activityName: String = "com.sunshine.freeform.ui.main.MainActivity"
18 | val userId: Int = 0
19 | val intent = Intent("com.sunshine.freeform.start_freeform").apply {
20 | setPackage("com.sunshine.freeform")
21 | //要启动小窗程序的包名:如com.sunshine.freeform
22 | putExtra("packageName", packageName)
23 | //要启动小窗的活动名称,请注意,该活动可能需要对外暴露才可启动。如com.sunshine.freeform.ui.main.MainActivity
24 | putExtra("activityName", activityName)
25 | //可选,默认为0。对于系统存在“应用分身”等情况,可以指定userId
26 | putExtra("userId", userId)
27 | }
28 | context.sendBroadcast(intent)
29 | ```
--------------------------------------------------------------------------------
/qa_zh-Hans.md:
--------------------------------------------------------------------------------
1 | # 米窗2.5版本常见问题(持续更新)
2 |
3 | ## 1.米窗打开部分界面时一闪而过,从此侧边栏、悬浮窗再也打不开了
4 | 如果你使用的是前台保活模式,并且打开的界面如果包含系统设置、授权界面、部分设备的分身选择等重要的系统界面,就会出现上述问题。解决方案:使用无障碍保活
5 |
6 | ## 2.米窗点击某个界面后全屏
7 | 尝试使用Xposed勾选系统框架并重启,Android 12+可以尝试勾选米窗中“使用Shizuku/Sui阻止应用跳出小窗到全屏”选项
8 |
9 | ## 3.无法通过米窗打开小窗/卡在引导/打开小窗闪退
10 | 产生该问题的原因可能是系统中设置过Vulkan。具体请见引导界面文字描述
11 |
12 | ## 4.打开小窗后,应用界面不能填满小窗,表现为类似Windows窗口
13 | 前往系统设置-开发者选项,关闭“强制使用桌面模式”
14 |
15 | ## 5.米窗掉后台
16 | 一些定制ROM可能产生该问题,建议为米窗加锁。米窗使用无障碍或前台服务保活,如果这两个服务都被杀掉的话,请考虑是否是系统问题?或尝试切换保活方式
17 |
18 | ## 6.界面缩放过大/过小
19 | 米窗支持单独对横屏/竖屏界面缩放调整,请分别调整
20 |
21 | ## 7.屏幕旋转后界面显示异常
22 | 某些低性能设备上可能产生该问题,如果出现该问题,可以尝试关闭米窗设置中“在屏幕旋转时修改小窗缩放比例”选项
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | maven { url 'https://jitpack.io' }
7 | }
8 | }
9 | dependencyResolutionManagement {
10 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
11 | repositories {
12 | google()
13 | mavenCentral()
14 | maven { url 'https://jitpack.io' }
15 | maven { url 'https://api.xposed.info' }
16 | }
17 | }
18 |
19 | rootProject.name = "Flyme-Freeform"
20 | include ':app'
21 | include ':hidden-api'
22 |
--------------------------------------------------------------------------------
/todo.md:
--------------------------------------------------------------------------------
1 | # 制作流体手势,通过无障碍,这样就可以监听返回了
--------------------------------------------------------------------------------