├── .gitattributes ├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── LICENSE ├── README.md ├── app ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── xposed_init │ ├── java │ └── de │ │ └── robv │ │ └── android │ │ └── xposed │ │ └── mods │ │ └── appsettings │ │ ├── Common.java │ │ ├── FilterItemComponent.java │ │ ├── RunAppWithLocale.java │ │ ├── XposedMod.java │ │ ├── XposedModActivity.java │ │ ├── hooks │ │ ├── Activities.java │ │ └── PackagePermissions.java │ │ └── settings │ │ ├── ApplicationSettings.java │ │ ├── LocaleList.java │ │ ├── PermissionSettings.java │ │ └── PermissionsListAdapter.java │ └── res │ ├── drawable-hdpi │ ├── ic_launcher.png │ └── ic_menu_save.png │ ├── drawable-ldpi │ ├── ic_launcher.png │ └── ic_menu_save.png │ ├── drawable-mdpi │ ├── ic_launcher.png │ └── ic_menu_save.png │ ├── drawable-xhdpi │ ├── ic_launcher.png │ └── ic_menu_save.png │ ├── layout │ ├── about.xml │ ├── app_list_item.xml │ ├── app_permission_item.xml │ ├── app_settings.xml │ ├── filter_dialog.xml │ ├── filter_item.xml │ ├── main.xml │ ├── permission_search.xml │ ├── permissions_dialog.xml │ └── recent_item.xml │ ├── menu │ ├── menu_app.xml │ └── menu_main.xml │ ├── values-cs │ └── strings.xml │ ├── values-de │ └── strings.xml │ ├── values-el │ └── strings.xml │ ├── values-es │ └── strings.xml │ ├── values-fr │ └── strings.xml │ ├── values-hu │ └── strings.xml │ ├── values-ja │ └── strings.xml │ ├── values-ko │ └── strings.xml │ ├── values-pt-rBR │ └── strings.xml │ ├── values-ru │ └── strings.xml │ ├── values-sk │ └── strings.xml │ ├── values-zh-rCN │ └── strings.xml │ ├── values-zh-rTW │ └── strings.xml │ └── values │ ├── attrs.xml │ ├── donottranslate.xml │ └── strings.xml ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshots ├── app_menu.png ├── app_permissions.png ├── app_resources.png ├── app_settings.png ├── apps_list.png ├── filter.png └── search_permissions.png └── settings.gradle /.gitattributes: -------------------------------------------------------------------------------- 1 | *.java text 2 | *.xml text 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/dictionaries 41 | .idea/libraries 42 | 43 | # Keystore files 44 | # Uncomment the following line if you do not want to check your keystore files in. 45 | #*.jks 46 | keystore.properties 47 | 48 | # External native build folder generated in Android Studio 2.2 and later 49 | .externalNativeBuild 50 | 51 | # Google Services (e.g. APIs or Firebase) 52 | google-services.json 53 | 54 | # Freeline 55 | freeline.py 56 | freeline/ 57 | freeline_project_description.json 58 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 1.8 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AppSettings 2 | =========== 3 | 4 | A modification using the Xposed framework to change settings like density per app. 5 | 6 | 7 | 8 | License 9 | ------- 10 | 11 | Copyright 2014 rovo89, Tungstwenty 12 | 13 | Licensed under the Apache License, Version 2.0 (the "License"); 14 | you may not use this file except in compliance with the License. 15 | You may obtain a copy of the License at 16 | 17 | http://www.apache.org/licenses/LICENSE-2.0 18 | 19 | Unless required by applicable law or agreed to in writing, software 20 | distributed under the License is distributed on an "AS IS" BASIS, 21 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22 | See the License for the specific language governing permissions and 23 | limitations under the License. 24 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 19 5 | buildToolsVersion "26.0.1" 6 | 7 | defaultConfig { 8 | applicationId "de.robv.android.xposed.mods.appsettings" 9 | minSdkVersion 15 10 | targetSdkVersion 15 11 | versionName "1.15" 12 | versionCode 33 13 | archivesBaseName = "App_Settings-v${versionName}" 14 | } 15 | 16 | lintOptions{ 17 | disable 'MissingTranslation' 18 | } 19 | 20 | /* 21 | If you want to sign your releases, create a file in the root of the project named keystore.properties with this content: 22 | storePassword=myStorePassword 23 | keyPassword=mykeyPassword 24 | keyAlias=myKeyAlias 25 | storeFile=myStoreFileLocation 26 | */ 27 | 28 | def keystorePropertiesFile = rootProject.file("keystore.properties") 29 | boolean doSign = keystorePropertiesFile.exists() 30 | 31 | if (doSign) { 32 | def keystoreProperties = new Properties() 33 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 34 | 35 | signingConfigs { 36 | release { 37 | keyAlias keystoreProperties['keyAlias'] 38 | keyPassword keystoreProperties['keyPassword'] 39 | storeFile file(keystoreProperties['storeFile']) 40 | storePassword keystoreProperties['storePassword'] 41 | } 42 | } 43 | } 44 | 45 | buildTypes { 46 | release { 47 | if (doSign) { 48 | signingConfig signingConfigs.release 49 | } 50 | minifyEnabled false 51 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 52 | } 53 | } 54 | } 55 | 56 | dependencies { 57 | provided 'de.robv.android.xposed:api:82' 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 37 | 38 | 39 | 42 | 43 | 44 | 45 | 46 | 47 | 50 | 53 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /app/src/main/assets/xposed_init: -------------------------------------------------------------------------------- 1 | de.robv.android.xposed.mods.appsettings.XposedMod 2 | -------------------------------------------------------------------------------- /app/src/main/java/de/robv/android/xposed/mods/appsettings/Common.java: -------------------------------------------------------------------------------- 1 | package de.robv.android.xposed.mods.appsettings; 2 | 3 | import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR; 4 | import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_USER; 5 | import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; 6 | import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; 7 | import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; 8 | import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; 9 | import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR; 10 | import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; 11 | import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; 12 | import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; 13 | import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE; 14 | import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT; 15 | import android.annotation.TargetApi; 16 | import android.app.Notification; 17 | import android.os.Build; 18 | 19 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 20 | public class Common { 21 | 22 | public static final String TAG = "AppSettings"; 23 | public static final String MY_PACKAGE_NAME = Common.class.getPackage().getName(); 24 | 25 | public static final String ACTION_PERMISSIONS = "update_permissions"; 26 | 27 | 28 | public static final String PREFS = "ModSettings"; 29 | 30 | public static final String PREF_DEFAULT = "default"; 31 | 32 | public static final String PREF_ACTIVE = "/active"; 33 | public static final String PREF_DPI = "/dpi"; 34 | public static final String PREF_FONT_SCALE = "/font-scale"; 35 | public static final String PREF_LOCALE = "/locale"; 36 | public static final String PREF_SCREEN = "/screen"; 37 | public static final String PREF_XLARGE = "/tablet"; 38 | public static final String PREF_RES_ON_WIDGETS = "/res-on-widgets"; 39 | public static final String PREF_RESIDENT = "/resident"; 40 | public static final String PREF_NO_FULLSCREEN_IME = "/no-fullscreen-ime"; 41 | public static final String PREF_NO_BIG_NOTIFICATIONS = "/no-big-notifications"; 42 | public static final String PREF_INSISTENT_NOTIF = "/insistent-notif"; 43 | public static final String PREF_ONGOING_NOTIF = "/ongoing-notif"; 44 | public static final String PREF_NOTIF_PRIORITY = "/notif-priority"; 45 | public static final String PREF_REVOKEPERMS = "/revoke-perms"; 46 | public static final String PREF_REVOKELIST = "/revoke-list"; 47 | public static final String PREF_FULLSCREEN = "/fullscreen"; 48 | public static final String PREF_NO_TITLE = "/no-title"; 49 | public static final String PREF_ALLOW_ON_LOCKSCREEN = "/allow-on-lockscreen"; 50 | public static final String PREF_SCREEN_ON = "/screen-on"; 51 | public static final String PREF_ORIENTATION = "/orientation"; 52 | public static final String PREF_RECENTS_MODE = "/recents-mode"; 53 | public static final String PREF_MUTE = "/mute"; 54 | public static final String PREF_LEGACY_MENU = "/legacy-menu"; 55 | 56 | public static final int[] swdp = { 0, 320, 480, 600, 800, 1000, 1080, 1440 }; 57 | public static final int[] wdp = { 0, 320, 480, 600, 800, 1000, 1080, 1440 }; 58 | public static final int[] hdp = { 0, 480, 854, 1024, 1280, 1600, 1920, 2560 }; 59 | 60 | public static int[] orientationCodes = { Integer.MIN_VALUE, 61 | SCREEN_ORIENTATION_UNSPECIFIED, 62 | SCREEN_ORIENTATION_PORTRAIT, SCREEN_ORIENTATION_LANDSCAPE, 63 | SCREEN_ORIENTATION_SENSOR, 64 | SCREEN_ORIENTATION_SENSOR_PORTRAIT, SCREEN_ORIENTATION_SENSOR_LANDSCAPE, 65 | SCREEN_ORIENTATION_REVERSE_PORTRAIT, SCREEN_ORIENTATION_REVERSE_LANDSCAPE, 66 | SCREEN_ORIENTATION_FULL_SENSOR, 67 | // These require API 18 68 | SCREEN_ORIENTATION_USER_PORTRAIT, SCREEN_ORIENTATION_USER_LANDSCAPE, 69 | SCREEN_ORIENTATION_FULL_USER }; 70 | { 71 | if (Build.VERSION.SDK_INT < 18) { 72 | // Strip out the last 3 entries 73 | int[] newCodes = new int[orientationCodes.length - 3]; 74 | System.arraycopy(orientationCodes, 0, newCodes, 0, orientationCodes.length - 3); 75 | orientationCodes = newCodes; 76 | } 77 | } 78 | public static int[] orientationLabels = { R.string.settings_default, 79 | R.string.settings_ori_normal, 80 | R.string.settings_ori_portrait, R.string.settings_ori_landscape, 81 | R.string.settings_ori_forceauto, 82 | R.string.settings_ori_portrait_sensor, R.string.settings_ori_landscape_sensor, 83 | R.string.settings_ori_portrait_reverse, R.string.settings_ori_landscape_reverse, 84 | R.string.settings_ori_forceauto_4way, 85 | // These require API 18 86 | R.string.settings_ori_portrait_user, R.string.settings_ori_landscape_user, 87 | R.string.settings_ori_user_4way }; 88 | { 89 | if (Build.VERSION.SDK_INT < 18) { 90 | // Strip out the last 3 entries 91 | int[] newLabels = new int[orientationLabels.length - 3]; 92 | System.arraycopy(orientationLabels, 0, newLabels, 0, orientationLabels.length - 3); 93 | orientationLabels = newLabels; 94 | } 95 | } 96 | 97 | public static final int[] notifPriCodes = { Integer.MIN_VALUE, 98 | Notification.PRIORITY_MAX, Notification.PRIORITY_HIGH, 99 | Notification.PRIORITY_DEFAULT, 100 | Notification.PRIORITY_LOW, Notification.PRIORITY_MIN }; 101 | public static final int[] notifPriLabels = { R.string.settings_default, 102 | R.string.settings_npri_max, R.string.settings_npri_high, 103 | R.string.settings_npri_normal, 104 | R.string.settings_npri_low, 105 | R.string.settings_npri_min }; 106 | 107 | public static final int FULLSCREEN_DEFAULT = 0; 108 | public static final int FULLSCREEN_FORCE = 1; 109 | public static final int FULLSCREEN_PREVENT = 2; 110 | public static final int FULLSCREEN_IMMERSIVE = 3; 111 | 112 | public static final int ONGOING_NOTIF_DEFAULT = 0; 113 | public static final int ONGOING_NOTIF_FORCE = 1; 114 | public static final int ONGOING_NOTIF_PREVENT = 2; 115 | 116 | public static final int PREF_RECENTS_DEFAULT = 0; 117 | public static final int PREF_RECENTS_FORCE = 1; 118 | public static final int PREF_RECENTS_PREVENT = 2; 119 | 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/java/de/robv/android/xposed/mods/appsettings/FilterItemComponent.java: -------------------------------------------------------------------------------- 1 | package de.robv.android.xposed.mods.appsettings; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.util.AttributeSet; 6 | import android.view.LayoutInflater; 7 | import android.widget.LinearLayout; 8 | import android.widget.RadioGroup; 9 | import android.widget.TextView; 10 | 11 | /** 12 | * Composite component that displays a header and a triplet of radio buttons for 13 | * selection of All / Overridden / Unchanged settings for each parameter 14 | */ 15 | public class FilterItemComponent extends LinearLayout { 16 | 17 | private OnFilterChangeListener listener; 18 | 19 | /** Constructor for designer instantiation */ 20 | public FilterItemComponent(Context context, AttributeSet attrs) { 21 | super(context, attrs); 22 | 23 | LayoutInflater.from(context).inflate(R.layout.filter_item, this); 24 | 25 | TypedArray atts = context.obtainStyledAttributes(attrs, R.styleable.FilterItem); 26 | 27 | // Load label values, if any 28 | setLabel(R.id.txtFilterName, atts.getString(R.styleable.FilterItem_label)); 29 | setLabel(R.id.radAll, atts.getString(R.styleable.FilterItem_all_label)); 30 | setLabel(R.id.radOverridden, atts.getString(R.styleable.FilterItem_overridden_label)); 31 | setLabel(R.id.radUnchanged, atts.getString(R.styleable.FilterItem_unchanged_label)); 32 | atts.recycle(); 33 | 34 | setupListener(); 35 | } 36 | 37 | /** Constructor for programmatic instantiation */ 38 | public FilterItemComponent(Context context, String filterName, String labelAll, String labelOverriden, String labelUnchanged) { 39 | super(context); 40 | 41 | LayoutInflater.from(context).inflate(R.layout.filter_item, this); 42 | 43 | // Load label values, if any 44 | setLabel(R.id.txtFilterName, filterName); 45 | setLabel(R.id.radAll, labelAll); 46 | setLabel(R.id.radOverridden, labelOverriden); 47 | setLabel(R.id.radUnchanged, labelUnchanged); 48 | 49 | setupListener(); 50 | } 51 | 52 | private void setupListener() { 53 | // Notify any listener of changes in the selected option 54 | ((RadioGroup) findViewById(R.id.radOptions)).setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { 55 | @Override 56 | public void onCheckedChanged(RadioGroup group, int checkedId) { 57 | if (listener != null) { 58 | switch (checkedId) { 59 | case R.id.radOverridden: 60 | listener.onFilterChanged(FilterItemComponent.this, FilterState.OVERRIDDEN); 61 | break; 62 | case R.id.radUnchanged: 63 | listener.onFilterChanged(FilterItemComponent.this, FilterState.UNCHANGED); 64 | break; 65 | default: 66 | listener.onFilterChanged(FilterItemComponent.this, FilterState.ALL); 67 | break; 68 | } 69 | } 70 | } 71 | }); 72 | } 73 | 74 | /* 75 | * Update the label of a view id, if non-null 76 | */ 77 | private void setLabel(int id, CharSequence value) { 78 | TextView label = (TextView) findViewById(id); 79 | if (label != null && value != null) { 80 | label.setText(value); 81 | } 82 | } 83 | 84 | /** 85 | * Enable or disable all the items within this compound component 86 | */ 87 | @Override 88 | public void setEnabled(boolean enabled) { 89 | findViewById(R.id.radOptions).setEnabled(enabled); 90 | findViewById(R.id.radAll).setEnabled(enabled); 91 | findViewById(R.id.radOverridden).setEnabled(enabled); 92 | findViewById(R.id.radUnchanged).setEnabled(enabled); 93 | } 94 | 95 | /** 96 | * Check if this compound component is enabled 97 | */ 98 | @Override 99 | public boolean isEnabled() { 100 | return findViewById(R.id.radOptions).isEnabled(); 101 | } 102 | 103 | /** 104 | * Get currently selected filter option 105 | */ 106 | public FilterState getFilterState() { 107 | switch (((RadioGroup) findViewById(R.id.radOptions)).getCheckedRadioButtonId()) { 108 | case R.id.radOverridden: 109 | return FilterState.OVERRIDDEN; 110 | case R.id.radUnchanged: 111 | return FilterState.UNCHANGED; 112 | default: 113 | return FilterState.ALL; 114 | } 115 | } 116 | 117 | /** 118 | * Activate one of the 3 options as the selected one 119 | */ 120 | public void setFilterState(FilterState state) { 121 | // Handle null values and use the default "All" 122 | if (state == null) 123 | state = FilterState.ALL; 124 | 125 | switch (state) { 126 | case OVERRIDDEN: 127 | ((RadioGroup) findViewById(R.id.radOptions)).check(R.id.radOverridden); 128 | break; 129 | case UNCHANGED: 130 | ((RadioGroup) findViewById(R.id.radOptions)).check(R.id.radUnchanged); 131 | break; 132 | default: 133 | ((RadioGroup) findViewById(R.id.radOptions)).check(R.id.radAll); 134 | break; 135 | } 136 | } 137 | 138 | /** 139 | * Register a listener to be notified when the selection changes 140 | */ 141 | public void setOnFilterChangeListener(OnFilterChangeListener listener) { 142 | this.listener = listener; 143 | } 144 | 145 | /** 146 | * Interface for listeners that will be notified of selection changes 147 | */ 148 | public static interface OnFilterChangeListener { 149 | /** 150 | * Notification that this filter item has changed to a new selected state 151 | */ 152 | public void onFilterChanged(FilterItemComponent item, FilterState state); 153 | } 154 | 155 | /** 156 | * Possible values for the filter state: All / Overridden / Unchanged 157 | */ 158 | public static enum FilterState { 159 | ALL, OVERRIDDEN, UNCHANGED; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /app/src/main/java/de/robv/android/xposed/mods/appsettings/RunAppWithLocale.java: -------------------------------------------------------------------------------- 1 | package de.robv.android.xposed.mods.appsettings; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.SharedPreferences; 7 | import android.os.Bundle; 8 | import android.os.Handler; 9 | import android.widget.Toast; 10 | 11 | public class RunAppWithLocale extends BroadcastReceiver { 12 | public static final String ACTION_RUN_APP_WITH_LOCALE = "appsettings.intent.action.RUN_APP_WITH_LOCALE"; 13 | 14 | @Override 15 | public void onReceive(final Context context, Intent intent) { 16 | String action = intent.getAction(); 17 | final SharedPreferences prefs = context.getSharedPreferences(Common.PREFS, Context.MODE_WORLD_READABLE); 18 | if (ACTION_RUN_APP_WITH_LOCALE.equals(action)) { 19 | Bundle extras = intent.getExtras(); 20 | if (extras == null) { 21 | return; 22 | } 23 | final String packageName = extras.getString("package"); 24 | final String newLocale = extras.getString("locale"); 25 | final boolean active = prefs.getBoolean(packageName + Common.PREF_ACTIVE, false); 26 | final String currentLocale = prefs.getString(packageName + Common.PREF_LOCALE, null); 27 | final SharedPreferences.Editor prefsEditor = prefs.edit(); 28 | prefsEditor.putBoolean(packageName + Common.PREF_ACTIVE, true); 29 | prefsEditor.putString(packageName + Common.PREF_LOCALE, newLocale); 30 | prefsEditor.apply(); 31 | Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName); 32 | if (launchIntent != null) { 33 | launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 34 | context.startActivity(launchIntent); 35 | } 36 | // TODO: check for current settings and restore them 37 | Handler revertBack = new Handler(); 38 | revertBack.postDelayed(new Runnable() { 39 | 40 | @Override 41 | public void run() { 42 | prefsEditor.putBoolean(packageName + Common.PREF_ACTIVE, false); 43 | prefsEditor.putString(packageName + Common.PREF_LOCALE, null); 44 | prefsEditor.apply(); 45 | } 46 | }, 5000); 47 | } 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /app/src/main/java/de/robv/android/xposed/mods/appsettings/hooks/Activities.java: -------------------------------------------------------------------------------- 1 | package de.robv.android.xposed.mods.appsettings.hooks; 2 | 3 | import static de.robv.android.xposed.XposedBridge.hookAllConstructors; 4 | import static de.robv.android.xposed.XposedBridge.hookMethod; 5 | import static de.robv.android.xposed.XposedHelpers.callMethod; 6 | import static de.robv.android.xposed.XposedHelpers.findAndHookMethod; 7 | import static de.robv.android.xposed.XposedHelpers.findClass; 8 | import static de.robv.android.xposed.XposedHelpers.findMethodExact; 9 | import static de.robv.android.xposed.XposedHelpers.getAdditionalInstanceField; 10 | import static de.robv.android.xposed.XposedHelpers.getObjectField; 11 | import static de.robv.android.xposed.XposedHelpers.getStaticIntField; 12 | import static de.robv.android.xposed.XposedHelpers.setAdditionalInstanceField; 13 | import static de.robv.android.xposed.XposedHelpers.setIntField; 14 | 15 | import java.lang.reflect.Method; 16 | 17 | import android.annotation.SuppressLint; 18 | import android.annotation.TargetApi; 19 | import android.app.Activity; 20 | import android.content.Context; 21 | import android.content.Intent; 22 | import android.content.pm.ActivityInfo; 23 | import android.inputmethodservice.InputMethodService; 24 | import android.os.Build; 25 | import android.view.View; 26 | import android.view.Window; 27 | import android.view.WindowManager; 28 | import android.view.inputmethod.EditorInfo; 29 | import android.view.inputmethod.InputConnection; 30 | import de.robv.android.xposed.XC_MethodHook; 31 | import de.robv.android.xposed.XposedBridge; 32 | import de.robv.android.xposed.mods.appsettings.Common; 33 | import de.robv.android.xposed.mods.appsettings.XposedMod; 34 | 35 | 36 | public class Activities { 37 | 38 | private static final String PROP_FULLSCREEN = "AppSettings-Fullscreen"; 39 | private static final String PROP_IMMERSIVE = "AppSettings-Immersive"; 40 | private static final String PROP_KEEP_SCREEN_ON = "AppSettings-KeepScreenOn"; 41 | private static final String PROP_LEGACY_MENU = "AppSettings-LegacyMenu"; 42 | private static final String PROP_ORIENTATION = "AppSettings-Orientation"; 43 | 44 | private static int FLAG_NEEDS_MENU_KEY = Build.VERSION.SDK_INT >= 22 ? 0 : getStaticIntField(WindowManager.LayoutParams.class, "FLAG_NEEDS_MENU_KEY"); 45 | private static String CLASS_PHONEWINDOW = Build.VERSION.SDK_INT >= 23 ? "com.android.internal.policy.PhoneWindow" : "com.android.internal.policy.impl.PhoneWindow"; 46 | private static String CLASS_PHONEWINDOW_DECORVIEW = null; 47 | public static void hookActivitySettings() { 48 | if (Build.VERSION.SDK_INT >= 24) { 49 | CLASS_PHONEWINDOW_DECORVIEW = "com.android.internal.policy.DecorView"; 50 | } else if (Build.VERSION.SDK_INT == 23) { 51 | CLASS_PHONEWINDOW_DECORVIEW = "com.android.internal.policy.PhoneWindow.DecorView"; 52 | } else { 53 | CLASS_PHONEWINDOW_DECORVIEW = "com.android.internal.policy.impl.PhoneWindow.DecorView"; 54 | } 55 | try { 56 | findAndHookMethod(CLASS_PHONEWINDOW, null, "generateLayout", 57 | CLASS_PHONEWINDOW_DECORVIEW, new XC_MethodHook() { 58 | 59 | @SuppressLint("InlinedApi") 60 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 61 | Window window = (Window) param.thisObject; 62 | View decorView = (View) param.args[0]; 63 | Context context = window.getContext(); 64 | String packageName = context.getPackageName(); 65 | 66 | if (!XposedMod.isActive(packageName)) 67 | return; 68 | 69 | int fullscreen; 70 | try { 71 | fullscreen = XposedMod.prefs.getInt(packageName + Common.PREF_FULLSCREEN, 72 | Common.FULLSCREEN_DEFAULT); 73 | } catch (ClassCastException ex) { 74 | // Legacy boolean setting 75 | fullscreen = XposedMod.prefs.getBoolean(packageName + Common.PREF_FULLSCREEN, false) 76 | ? Common.FULLSCREEN_FORCE : Common.FULLSCREEN_DEFAULT; 77 | } 78 | if (fullscreen == Common.FULLSCREEN_FORCE) { 79 | window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); 80 | setAdditionalInstanceField(window, PROP_FULLSCREEN, Boolean.TRUE); 81 | } else if (fullscreen == Common.FULLSCREEN_PREVENT) { 82 | window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); 83 | setAdditionalInstanceField(window, PROP_FULLSCREEN, Boolean.FALSE); 84 | } else if (fullscreen == Common.FULLSCREEN_IMMERSIVE && Build.VERSION.SDK_INT >= 19) { 85 | window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); 86 | setAdditionalInstanceField(window, PROP_FULLSCREEN, Boolean.TRUE); 87 | setAdditionalInstanceField(decorView, PROP_IMMERSIVE, Boolean.TRUE); 88 | decorView.setSystemUiVisibility( 89 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 90 | | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 91 | | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); 92 | } 93 | 94 | if (XposedMod.prefs.getBoolean(packageName + Common.PREF_NO_TITLE, false)) 95 | window.requestFeature(Window.FEATURE_NO_TITLE); 96 | 97 | if (XposedMod.prefs.getBoolean(packageName + Common.PREF_ALLOW_ON_LOCKSCREEN, false)) 98 | window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | 99 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | 100 | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); 101 | 102 | if (XposedMod.prefs.getBoolean(packageName + Common.PREF_SCREEN_ON, false)) { 103 | window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 104 | setAdditionalInstanceField(window, PROP_KEEP_SCREEN_ON, Boolean.TRUE); 105 | } 106 | 107 | if (XposedMod.prefs.getBoolean(packageName + Common.PREF_LEGACY_MENU, false)) { 108 | if (Build.VERSION.SDK_INT >= 22) { 109 | // NEEDS_MENU_SET_TRUE = 1 110 | callMethod(window, "setNeedsMenuKey", 1); 111 | } 112 | else { 113 | window.setFlags(FLAG_NEEDS_MENU_KEY, FLAG_NEEDS_MENU_KEY); 114 | setAdditionalInstanceField(window, PROP_LEGACY_MENU, Boolean.TRUE); 115 | } 116 | } 117 | 118 | int orientation = XposedMod.prefs.getInt(packageName + Common.PREF_ORIENTATION, XposedMod.prefs.getInt(Common.PREF_DEFAULT + Common.PREF_ORIENTATION, 0)); 119 | if (orientation > 0 && orientation < Common.orientationCodes.length && context instanceof Activity) { 120 | ((Activity) context).setRequestedOrientation(Common.orientationCodes[orientation]); 121 | setAdditionalInstanceField(context, PROP_ORIENTATION, orientation); 122 | } 123 | } 124 | }); 125 | } catch (Throwable e) { 126 | XposedBridge.log(e); 127 | } 128 | 129 | try { 130 | findAndHookMethod(Window.class, "setFlags", int.class, int.class, 131 | new XC_MethodHook() { 132 | @Override 133 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 134 | 135 | int flags = (Integer) param.args[0]; 136 | int mask = (Integer) param.args[1]; 137 | if ((mask & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0) { 138 | Boolean fullscreen = (Boolean) getAdditionalInstanceField(param.thisObject, PROP_FULLSCREEN); 139 | if (fullscreen != null) { 140 | if (fullscreen.booleanValue()) { 141 | flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; 142 | } else { 143 | flags &= ~WindowManager.LayoutParams.FLAG_FULLSCREEN; 144 | } 145 | param.args[0] = flags; 146 | } 147 | } 148 | if ((mask & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0) { 149 | Boolean keepScreenOn = (Boolean) getAdditionalInstanceField(param.thisObject, PROP_KEEP_SCREEN_ON); 150 | if (keepScreenOn != null) { 151 | if (keepScreenOn.booleanValue()) { 152 | flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; 153 | } 154 | param.args[0] = flags; 155 | } 156 | } 157 | if ((mask & FLAG_NEEDS_MENU_KEY) != 0) { 158 | Boolean menu = (Boolean) getAdditionalInstanceField(param.thisObject, PROP_LEGACY_MENU); 159 | if (menu != null) { 160 | if (menu.booleanValue()) { 161 | flags |= FLAG_NEEDS_MENU_KEY; 162 | } 163 | param.args[0] = flags; 164 | } 165 | } 166 | } 167 | }); 168 | } catch (Throwable e) { 169 | XposedBridge.log(e); 170 | } 171 | 172 | if (Build.VERSION.SDK_INT >= 19) { 173 | try { 174 | findAndHookMethod("android.view.ViewRootImpl", null, "dispatchSystemUiVisibilityChanged", 175 | int.class, int.class, int.class, int.class, new XC_MethodHook() { 176 | @TargetApi(19) 177 | @Override 178 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 179 | // Has the navigation bar been shown? 180 | int localChanges = (Integer) param.args[3]; 181 | if ((localChanges & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) 182 | return; 183 | 184 | // Should it be hidden? 185 | View decorView = (View) getObjectField(param.thisObject, "mView"); 186 | Boolean immersive = (decorView == null) 187 | ? null 188 | : (Boolean) getAdditionalInstanceField(decorView, PROP_IMMERSIVE); 189 | if (immersive == null || !immersive.booleanValue()) 190 | return; 191 | 192 | // Enforce SYSTEM_UI_FLAG_HIDE_NAVIGATION and hide changes to this flag 193 | int globalVisibility = (Integer) param.args[1]; 194 | int localValue = (Integer) param.args[2]; 195 | param.args[1] = globalVisibility | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; 196 | param.args[2] = localValue | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; 197 | param.args[3] = localChanges & ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; 198 | } 199 | }); 200 | } catch (Throwable e) { 201 | XposedBridge.log(e); 202 | } 203 | } 204 | 205 | try { 206 | findAndHookMethod(Activity.class, "setRequestedOrientation", int.class, new XC_MethodHook() { 207 | @Override 208 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 209 | Integer orientation = (Integer) getAdditionalInstanceField(param.thisObject, PROP_ORIENTATION); 210 | if (orientation != null) 211 | param.args[0] = Common.orientationCodes[orientation]; 212 | } 213 | }); 214 | } catch (Throwable e) { 215 | XposedBridge.log(e); 216 | } 217 | 218 | try { 219 | findAndHookMethod(InputMethodService.class, "doStartInput", 220 | InputConnection.class, EditorInfo.class, boolean.class, new XC_MethodHook() { 221 | @Override 222 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 223 | EditorInfo info = (EditorInfo) param.args[1]; 224 | if (info != null && info.packageName != null) { 225 | XposedMod.prefs.reload(); 226 | if (XposedMod.isActive(info.packageName, Common.PREF_NO_FULLSCREEN_IME)) 227 | info.imeOptions |= EditorInfo.IME_FLAG_NO_FULLSCREEN; 228 | } 229 | } 230 | }); 231 | } catch (Throwable e) { 232 | XposedBridge.log(e); 233 | } 234 | } 235 | 236 | public static void hookActivitySettingsInSystemServer(ClassLoader classLoader) { 237 | try { 238 | // Hook one of the several variations of ActivityStack.realStartActivityLocked from different ROMs 239 | Method mthRealStartActivityLocked; 240 | if (Build.VERSION.SDK_INT <= 18) { 241 | try { 242 | mthRealStartActivityLocked = findMethodExact("com.android.server.am.ActivityStack", classLoader, "realStartActivityLocked", 243 | "com.android.server.am.ActivityRecord", "com.android.server.am.ProcessRecord", 244 | boolean.class, boolean.class, boolean.class); 245 | } catch (NoSuchMethodError t) { 246 | mthRealStartActivityLocked = findMethodExact("com.android.server.am.ActivityStack", classLoader, "realStartActivityLocked", 247 | "com.android.server.am.ActivityRecord", "com.android.server.am.ProcessRecord", 248 | boolean.class, boolean.class); 249 | } 250 | } else { 251 | mthRealStartActivityLocked = findMethodExact("com.android.server.am.ActivityStackSupervisor", classLoader, "realStartActivityLocked", 252 | "com.android.server.am.ActivityRecord", "com.android.server.am.ProcessRecord", 253 | boolean.class, boolean.class); 254 | } 255 | hookMethod(mthRealStartActivityLocked, new XC_MethodHook() { 256 | @Override 257 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 258 | String pkgName = (String) getObjectField(param.args[0], "packageName"); 259 | if (XposedMod.isActive(pkgName, Common.PREF_RESIDENT)) { 260 | int adj = -12; 261 | Object proc = getObjectField(param.args[0], "app"); 262 | 263 | // Override the *Adj values if meant to be resident in memory 264 | if (proc != null) { 265 | setIntField(proc, "maxAdj", adj); 266 | if (Build.VERSION.SDK_INT <= 18) 267 | setIntField(proc, "hiddenAdj", adj); 268 | setIntField(proc, "curRawAdj", adj); 269 | setIntField(proc, "setRawAdj", adj); 270 | setIntField(proc, "curAdj", adj); 271 | setIntField(proc, "setAdj", adj); 272 | } 273 | } 274 | } 275 | }); 276 | } catch (Throwable e) { 277 | XposedBridge.log(e); 278 | } 279 | 280 | try { 281 | hookAllConstructors(findClass("com.android.server.am.ActivityRecord", classLoader), new XC_MethodHook() { 282 | @Override 283 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 284 | ActivityInfo aInfo = (ActivityInfo) getObjectField(param.thisObject, "info"); 285 | if (aInfo == null) 286 | return; 287 | String pkgName = aInfo.packageName; 288 | if (XposedMod.prefs.getInt(pkgName + Common.PREF_RECENTS_MODE, Common.PREF_RECENTS_DEFAULT) > 0) { 289 | int recentsMode = XposedMod.prefs.getInt(pkgName + Common.PREF_RECENTS_MODE, Common.PREF_RECENTS_DEFAULT); 290 | if (recentsMode == Common.PREF_RECENTS_DEFAULT) 291 | return; 292 | Intent intent = (Intent) getObjectField(param.thisObject, "intent"); 293 | if (recentsMode == Common.PREF_RECENTS_FORCE) { 294 | int flags = (intent.getFlags() & ~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 295 | intent.setFlags(flags); 296 | } 297 | else if (recentsMode == Common.PREF_RECENTS_PREVENT) 298 | intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 299 | } 300 | } 301 | }); 302 | } catch (Throwable e) { 303 | XposedBridge.log(e); 304 | } 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /app/src/main/java/de/robv/android/xposed/mods/appsettings/hooks/PackagePermissions.java: -------------------------------------------------------------------------------- 1 | package de.robv.android.xposed.mods.appsettings.hooks; 2 | 3 | import static de.robv.android.xposed.XposedHelpers.callMethod; 4 | import static de.robv.android.xposed.XposedHelpers.findAndHookMethod; 5 | import static de.robv.android.xposed.XposedHelpers.findClass; 6 | import static de.robv.android.xposed.XposedHelpers.getObjectField; 7 | import static de.robv.android.xposed.XposedHelpers.setObjectField; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Map; 11 | import java.util.Set; 12 | 13 | import android.content.BroadcastReceiver; 14 | import android.content.Context; 15 | import android.content.Intent; 16 | import android.content.IntentFilter; 17 | import android.content.pm.ApplicationInfo; 18 | import android.os.Build; 19 | import android.util.Log; 20 | import de.robv.android.xposed.XC_MethodHook; 21 | import de.robv.android.xposed.XposedBridge; 22 | import de.robv.android.xposed.mods.appsettings.Common; 23 | import de.robv.android.xposed.mods.appsettings.XposedMod; 24 | 25 | public class PackagePermissions extends BroadcastReceiver { 26 | private final Object pmSvc; 27 | private final Map mPackages; 28 | private final Object mSettings; 29 | 30 | @SuppressWarnings("unchecked") 31 | public PackagePermissions(Object pmSvc) { 32 | this.pmSvc = pmSvc; 33 | this.mPackages = (Map) getObjectField(pmSvc, "mPackages"); 34 | this.mSettings = getObjectField(pmSvc, "mSettings"); 35 | } 36 | 37 | public static void initHooks(ClassLoader classLoader) { 38 | /* Hook to the PackageManager service in order to 39 | * - Listen for broadcasts to apply new settings and restart the app 40 | * - Intercept the permission granting function to remove disabled permissions 41 | */ 42 | try { 43 | final Class clsPMS = findClass("com.android.server.pm.PackageManagerService", classLoader); 44 | 45 | // Listen for broadcasts from the Settings part of the mod, so it's applied immediately 46 | findAndHookMethod(clsPMS, "systemReady", new XC_MethodHook() { 47 | @Override 48 | protected void afterHookedMethod(MethodHookParam param) 49 | throws Throwable { 50 | Context mContext = (Context) getObjectField(param.thisObject, "mContext"); 51 | mContext.registerReceiver(new PackagePermissions(param.thisObject), 52 | new IntentFilter(Common.MY_PACKAGE_NAME + ".UPDATE_PERMISSIONS"), 53 | Common.MY_PACKAGE_NAME + ".BROADCAST_PERMISSION", 54 | null); 55 | } 56 | }); 57 | 58 | // if the user has disabled certain permissions for an app, do as if the hadn't requested them 59 | XC_MethodHook hookGrantPermissions = new XC_MethodHook() { 60 | @SuppressWarnings("unchecked") 61 | @Override 62 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 63 | String pkgName = (String) getObjectField(param.args[0], "packageName"); 64 | if (!XposedMod.isActive(pkgName) || !XposedMod.prefs.getBoolean(pkgName + Common.PREF_REVOKEPERMS, false)) 65 | return; 66 | 67 | Set disabledPermissions = XposedMod.prefs.getStringSet(pkgName + Common.PREF_REVOKELIST, null); 68 | if (disabledPermissions == null || disabledPermissions.isEmpty()) 69 | return; 70 | 71 | ArrayList origRequestedPermissions = (ArrayList) getObjectField(param.args[0], "requestedPermissions"); 72 | param.setObjectExtra("orig_requested_permissions", origRequestedPermissions); 73 | 74 | ArrayList newRequestedPermissions = new ArrayList(origRequestedPermissions.size()); 75 | for (String perm: origRequestedPermissions) { 76 | if (!disabledPermissions.contains(perm)) 77 | newRequestedPermissions.add(perm); 78 | else 79 | // you requested those internet permissions? I didn't read that, sorry 80 | Log.w(Common.TAG, "Not granting permission " + perm 81 | + " to package " + pkgName 82 | + " because you think it should not have it"); 83 | } 84 | 85 | setObjectField(param.args[0], "requestedPermissions", newRequestedPermissions); 86 | } 87 | 88 | @SuppressWarnings("unchecked") 89 | @Override 90 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 91 | // restore requested permissions if they were modified 92 | ArrayList origRequestedPermissions = (ArrayList) param.getObjectExtra("orig_requested_permissions"); 93 | if (origRequestedPermissions != null) 94 | setObjectField(param.args[0], "requestedPermissions", origRequestedPermissions); 95 | } 96 | }; 97 | if (Build.VERSION.SDK_INT < 21) { 98 | findAndHookMethod(clsPMS, "grantPermissionsLPw", "android.content.pm.PackageParser$Package", boolean.class, hookGrantPermissions); 99 | } else { 100 | findAndHookMethod(clsPMS, "grantPermissionsLPw", "android.content.pm.PackageParser$Package", boolean.class, String.class, hookGrantPermissions); 101 | } 102 | } catch (Throwable e) { 103 | XposedBridge.log(e); 104 | } 105 | } 106 | 107 | @Override 108 | public void onReceive(Context context, Intent intent) { 109 | try { 110 | // The app broadcasted a request to update settings for a running app 111 | 112 | // Validate the action being requested 113 | if (!Common.ACTION_PERMISSIONS.equals(intent.getExtras().getString("action"))) 114 | return; 115 | 116 | String pkgName = intent.getExtras().getString("Package"); 117 | boolean killApp = intent.getExtras().getBoolean("Kill", false); 118 | 119 | XposedMod.prefs.reload(); 120 | 121 | Object pkgInfo; 122 | synchronized (mPackages) { 123 | pkgInfo = mPackages.get(pkgName); 124 | if (Build.VERSION.SDK_INT < 21) { 125 | callMethod(pmSvc, "grantPermissionsLPw", pkgInfo, true); 126 | } else { 127 | callMethod(pmSvc, "grantPermissionsLPw", pkgInfo, true, pkgName); 128 | } 129 | callMethod(mSettings, "writeLPr"); 130 | } 131 | 132 | // Apply new permissions if needed 133 | if (killApp) { 134 | try { 135 | ApplicationInfo appInfo = (ApplicationInfo) getObjectField(pkgInfo, "applicationInfo"); 136 | if (Build.VERSION.SDK_INT <= 18) 137 | callMethod(pmSvc, "killApplication", pkgName, appInfo.uid); 138 | else 139 | callMethod(pmSvc, "killApplication", pkgName, appInfo.uid, "apply App Settings"); 140 | } catch (Throwable t) { 141 | XposedBridge.log(t); 142 | } 143 | } 144 | } catch (Throwable t) { 145 | XposedBridge.log(t); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /app/src/main/java/de/robv/android/xposed/mods/appsettings/settings/LocaleList.java: -------------------------------------------------------------------------------- 1 | package de.robv.android.xposed.mods.appsettings.settings; 2 | 3 | import java.text.Collator; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import java.util.Locale; 7 | 8 | import android.content.res.Resources; 9 | 10 | /** 11 | * Manages a list of valid locales for the system 12 | */ 13 | public class LocaleList { 14 | 15 | /* 16 | * From AOSP code - listing available languages to present to the user 17 | */ 18 | private static class LocaleInfo implements Comparable { 19 | static final Collator sCollator = Collator.getInstance(); 20 | 21 | String label; 22 | Locale locale; 23 | 24 | public LocaleInfo(String label, Locale locale) { 25 | this.label = label; 26 | this.locale = locale; 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return this.label; 32 | } 33 | 34 | @Override 35 | public int compareTo(LocaleInfo another) { 36 | return sCollator.compare(this.label, another.label); 37 | } 38 | } 39 | 40 | private String[] localeCodes; 41 | private String[] localeDescriptions; 42 | 43 | public LocaleList(String defaultLabel) { 44 | final String[] locales = Resources.getSystem().getAssets().getLocales(); 45 | Arrays.sort(locales); 46 | final int origSize = locales.length; 47 | final LocaleInfo[] preprocess = new LocaleInfo[origSize]; 48 | int finalSize = 0; 49 | for (int i = 0; i < origSize; i++) { 50 | final String s = locales[i]; 51 | final int len = s.length(); 52 | if (len == 5) { 53 | String language = s.substring(0, 2); 54 | String country = s.substring(3, 5); 55 | final Locale l = new Locale(language, country); 56 | 57 | if (finalSize == 0) { 58 | preprocess[finalSize++] = new LocaleInfo(toTitleCase(l.getDisplayLanguage(l)), l); 59 | } else { 60 | // check previous entry: 61 | // same lang and a country -> upgrade to full name and 62 | // insert ours with full name 63 | // diff lang -> insert ours with lang-only name 64 | if (preprocess[finalSize - 1].locale.getLanguage().equals(language)) { 65 | preprocess[finalSize - 1].label = toTitleCase(getDisplayName(preprocess[finalSize - 1].locale)); 66 | preprocess[finalSize++] = new LocaleInfo(toTitleCase(getDisplayName(l)), l); 67 | } else { 68 | String displayName; 69 | if (s.equals("zz_ZZ")) { 70 | displayName = "Pseudo..."; 71 | } else { 72 | displayName = toTitleCase(l.getDisplayLanguage(l)); 73 | } 74 | preprocess[finalSize++] = new LocaleInfo(displayName, l); 75 | } 76 | } 77 | } 78 | } 79 | 80 | final LocaleInfo[] localeInfos = new LocaleInfo[finalSize]; 81 | for (int i = 0; i < finalSize; i++) { 82 | localeInfos[i] = preprocess[i]; 83 | } 84 | Arrays.sort(localeInfos); 85 | 86 | localeCodes = new String[localeInfos.length + 1]; 87 | localeDescriptions = new String[localeInfos.length + 1]; 88 | localeCodes[0] = ""; 89 | localeDescriptions[0] = defaultLabel; 90 | for (int i = 1; i < finalSize + 1; i++) { 91 | localeCodes[i] = getLocaleCode(localeInfos[i - 1].locale); 92 | localeDescriptions[i] = localeInfos[i - 1].label; 93 | } 94 | } 95 | 96 | private static String toTitleCase(String s) { 97 | if (s.length() == 0) { 98 | return s; 99 | } 100 | 101 | return Character.toUpperCase(s.charAt(0)) + s.substring(1); 102 | } 103 | 104 | private static String getDisplayName(Locale loc) { 105 | return loc.getDisplayName(loc); 106 | } 107 | 108 | private static String getLocaleCode(Locale loc) { 109 | String result = loc.getLanguage(); 110 | if (loc.getCountry().length() > 0) 111 | result += "_" + loc.getCountry(); 112 | if (loc.getVariant().length() > 0) 113 | result += "_" + loc.getVariant(); 114 | return result; 115 | } 116 | 117 | /** 118 | * Retrieve the locale code at a specific position in the list. 119 | */ 120 | public String getLocale(int pos) { 121 | return localeCodes[pos]; 122 | } 123 | 124 | /** 125 | * Retrieve the position where the specified locale code is, or 0 if it was 126 | * not found. 127 | */ 128 | public int getLocalePos(String locale) { 129 | for (int i = 1; i < localeCodes.length; i++) { 130 | if (localeCodes[i].equals(locale)) 131 | return i; 132 | } 133 | return 0; 134 | } 135 | 136 | /** 137 | * Retrieve an ordered list of the locale descriptions 138 | */ 139 | public List getDescriptionList() { 140 | return Arrays.asList(localeDescriptions); 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /app/src/main/java/de/robv/android/xposed/mods/appsettings/settings/PermissionSettings.java: -------------------------------------------------------------------------------- 1 | package de.robv.android.xposed.mods.appsettings.settings; 2 | 3 | import java.util.Collections; 4 | import java.util.Comparator; 5 | import java.util.HashSet; 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | import java.util.Set; 9 | 10 | import android.annotation.SuppressLint; 11 | import android.app.Activity; 12 | import android.app.Dialog; 13 | import android.content.pm.PackageInfo; 14 | import android.content.pm.PackageManager; 15 | import android.content.pm.PackageManager.NameNotFoundException; 16 | import android.content.pm.PermissionInfo; 17 | import android.graphics.Color; 18 | import android.view.View; 19 | import android.widget.Button; 20 | import android.widget.CompoundButton; 21 | import android.widget.ListView; 22 | import android.widget.Switch; 23 | import de.robv.android.xposed.mods.appsettings.R; 24 | 25 | 26 | /** 27 | * Manages a popup dialog for editing the Permission Revoking settings for a package 28 | */ 29 | public class PermissionSettings { 30 | private Dialog dialog; 31 | 32 | private OnDismissListener onOkListener; 33 | private OnDismissListener onCancelListener; 34 | 35 | boolean revokeActive; 36 | private Set disabledPerms; 37 | 38 | private List permsList = new LinkedList(); 39 | 40 | /** 41 | * Prepare a dialog for editing the permissions for the supplied package, 42 | * with the provided owner activity and initial settings 43 | */ 44 | public PermissionSettings(Activity owner, String pkgName, boolean revoking, Set disabledPermissions) throws NameNotFoundException { 45 | dialog = new Dialog(owner); 46 | dialog.setContentView(R.layout.permissions_dialog); 47 | dialog.setTitle(R.string.perms_title); 48 | dialog.setCancelable(true); 49 | dialog.setOwnerActivity(owner); 50 | 51 | revokeActive = revoking; 52 | if (disabledPermissions != null) 53 | disabledPerms = new HashSet(disabledPermissions); 54 | else 55 | disabledPerms = new HashSet(); 56 | 57 | Switch swtRevoke = (Switch) dialog.findViewById(R.id.swtRevokePerms); 58 | swtRevoke.setChecked(revokeActive); 59 | 60 | // Load the list of permissions for the package and present them 61 | loadPermissionsList(pkgName); 62 | 63 | final PermissionsListAdapter appListAdapter = new PermissionsListAdapter(owner, permsList, disabledPerms, true); 64 | appListAdapter.setCanEdit(revokeActive); 65 | ((ListView) dialog.findViewById(R.id.lstPermissions)).setAdapter(appListAdapter); 66 | 67 | // Track changes to the Revoke checkbox to lock or unlock the list of 68 | // permissions 69 | swtRevoke.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 70 | @Override 71 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 72 | revokeActive = isChecked; 73 | dialog.findViewById(R.id.lstPermissions).setBackgroundColor(revokeActive ? Color.BLACK : Color.DKGRAY); 74 | appListAdapter.setCanEdit(revokeActive); 75 | } 76 | }); 77 | dialog.findViewById(R.id.lstPermissions).setBackgroundColor(revokeActive ? Color.BLACK : Color.DKGRAY); 78 | 79 | ((Button) dialog.findViewById(R.id.btnPermsCancel)).setOnClickListener(new View.OnClickListener() { 80 | @Override 81 | public void onClick(View v) { 82 | if (onCancelListener != null) 83 | onCancelListener.onDismiss(PermissionSettings.this); 84 | dialog.dismiss(); 85 | } 86 | }); 87 | ((Button) dialog.findViewById(R.id.btnPermsOk)).setOnClickListener(new View.OnClickListener() { 88 | @Override 89 | public void onClick(View v) { 90 | if (onOkListener != null) 91 | onOkListener.onDismiss(PermissionSettings.this); 92 | dialog.dismiss(); 93 | } 94 | }); 95 | } 96 | 97 | /** 98 | * Display the editor dialog 99 | */ 100 | public void display() { 101 | dialog.show(); 102 | } 103 | 104 | /** 105 | * Register a listener to be invoked when the editor is dismissed with the 106 | * Ok button 107 | */ 108 | public void setOnOkListener(OnDismissListener listener) { 109 | onOkListener = listener; 110 | } 111 | 112 | /** 113 | * Register a listener to be invoked when the editor is dismissed with the 114 | * Cancel button 115 | */ 116 | public void setOnCancelListener(OnDismissListener listener) { 117 | onCancelListener = listener; 118 | } 119 | 120 | /** 121 | * Get the state of the Active switch 122 | */ 123 | public boolean getRevokeActive() { 124 | return revokeActive; 125 | } 126 | 127 | /** 128 | * Get the list of permissions in the disabled state 129 | */ 130 | public Set getDisabledPermissions() { 131 | return new HashSet(disabledPerms); 132 | } 133 | 134 | /* 135 | * Populate the list of permissions requested by this package 136 | */ 137 | @SuppressLint("DefaultLocale") 138 | private void loadPermissionsList(String pkgName) throws NameNotFoundException { 139 | permsList.clear(); 140 | 141 | PackageManager pm = dialog.getContext().getPackageManager(); 142 | PackageInfo pkgInfo = pm.getPackageInfo(pkgName, PackageManager.GET_PERMISSIONS); 143 | if (pkgInfo.sharedUserId != null) { 144 | Switch swtRevoke = (Switch) dialog.findViewById(R.id.swtRevokePerms); 145 | swtRevoke.setText(R.string.perms_shared_warning); 146 | swtRevoke.setTextColor(Color.RED); 147 | } 148 | String[] permissions = pkgInfo.requestedPermissions; 149 | if (permissions == null) { 150 | permissions = new String[0]; 151 | } 152 | for (String perm : permissions) { 153 | try { 154 | permsList.add(pm.getPermissionInfo(perm, 0)); 155 | } catch (NameNotFoundException e) { 156 | PermissionInfo unknownPerm = new PermissionInfo(); 157 | unknownPerm.name = perm; 158 | permsList.add(unknownPerm); 159 | } 160 | } 161 | 162 | Collections.sort(permsList, new Comparator() { 163 | @Override 164 | public int compare(PermissionInfo lhs, PermissionInfo rhs) { 165 | if (lhs.name == null) { 166 | return -1; 167 | } else if (rhs.name == null) { 168 | return 1; 169 | } else { 170 | return lhs.name.toUpperCase().compareTo(rhs.name.toUpperCase()); 171 | } 172 | } 173 | }); 174 | } 175 | 176 | /** 177 | * Interface for the listeners of Ok/Cancel dismiss actions 178 | */ 179 | public static interface OnDismissListener { 180 | 181 | public abstract void onDismiss(PermissionSettings obj); 182 | } 183 | 184 | } 185 | -------------------------------------------------------------------------------- /app/src/main/java/de/robv/android/xposed/mods/appsettings/settings/PermissionsListAdapter.java: -------------------------------------------------------------------------------- 1 | package de.robv.android.xposed.mods.appsettings.settings; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Set; 6 | 7 | import android.app.Activity; 8 | import android.content.pm.PackageManager; 9 | import android.content.pm.PermissionInfo; 10 | import android.graphics.Color; 11 | import android.graphics.Paint; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.widget.ArrayAdapter; 15 | import android.widget.Filter; 16 | import android.widget.Filterable; 17 | import android.widget.TextView; 18 | import de.robv.android.xposed.mods.appsettings.R; 19 | 20 | /* 21 | * Adapter to feed the list of permission entries 22 | */ 23 | public class PermissionsListAdapter extends ArrayAdapter implements Filterable { 24 | 25 | Activity context; 26 | private List originalPermsList; 27 | private Set disabledPerms; 28 | private boolean allowEdits; 29 | private boolean canEdit; 30 | private Filter mFilter; 31 | 32 | public PermissionsListAdapter(Activity context, List items, Set disabledPerms, boolean allowEdits) { 33 | super(context, R.layout.app_permission_item, items); 34 | this.context = context; 35 | originalPermsList = new ArrayList(items); 36 | this.disabledPerms = disabledPerms; 37 | this.allowEdits = allowEdits; 38 | canEdit = false; 39 | } 40 | 41 | public void setCanEdit(boolean canEdit) { 42 | this.canEdit = canEdit; 43 | } 44 | 45 | private class ViewHolder { 46 | TextView tvName; 47 | TextView tvDescription; 48 | } 49 | 50 | public View getView(int position, View convertView, ViewGroup parent) { 51 | View row = convertView; 52 | ViewHolder vHolder; 53 | if (row == null) { 54 | row = context.getLayoutInflater().inflate(R.layout.app_permission_item, parent, false); 55 | vHolder = new ViewHolder(); 56 | vHolder.tvName = (TextView) row.findViewById(R.id.perm_name); 57 | vHolder.tvDescription = (TextView) row.findViewById(R.id.perm_description); 58 | row.setTag(vHolder); 59 | } else { 60 | vHolder = (ViewHolder) row.getTag(); 61 | } 62 | 63 | PermissionInfo perm = getItem(position); 64 | PackageManager pm = context.getPackageManager(); 65 | 66 | CharSequence label = perm.loadLabel(pm); 67 | if (!label.equals(perm.name)) { 68 | label = perm.name + " (" + label + ")"; 69 | } 70 | 71 | vHolder.tvName.setText(label); 72 | CharSequence description = perm.loadDescription(pm); 73 | description = (description == null) ? "" : description.toString().trim(); 74 | if (description.length() == 0) 75 | description = context.getString(R.string.perms_nodescription); 76 | vHolder.tvDescription.setText(description); 77 | switch (perm.protectionLevel) { 78 | case PermissionInfo.PROTECTION_DANGEROUS: 79 | vHolder.tvDescription.setTextColor(Color.RED); 80 | break; 81 | case PermissionInfo.PROTECTION_SIGNATURE: 82 | vHolder.tvDescription.setTextColor(Color.GREEN); 83 | break; 84 | case PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM: 85 | vHolder.tvDescription.setTextColor(Color.YELLOW); 86 | break; 87 | default: 88 | vHolder.tvDescription.setTextColor(Color.parseColor("#0099CC")); 89 | break; 90 | } 91 | 92 | vHolder.tvName.setTag(perm.name); 93 | if (disabledPerms.contains(perm.name)) { 94 | vHolder.tvName.setPaintFlags(vHolder.tvName.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); 95 | vHolder.tvName.setTextColor(Color.MAGENTA); 96 | } else { 97 | vHolder.tvName.setPaintFlags(vHolder.tvName.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG)); 98 | vHolder.tvName.setTextColor(Color.WHITE); 99 | } 100 | if (allowEdits) { 101 | row.setOnClickListener(new View.OnClickListener() { 102 | @Override 103 | public void onClick(View v) { 104 | if (!canEdit) { 105 | return; 106 | } 107 | 108 | TextView tv = (TextView) v.findViewById(R.id.perm_name); 109 | if ((tv.getPaintFlags() & Paint.STRIKE_THRU_TEXT_FLAG) != 0) { 110 | disabledPerms.remove(tv.getTag()); 111 | tv.setPaintFlags(tv.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG)); 112 | tv.setTextColor(Color.WHITE); 113 | } else { 114 | disabledPerms.add((String) tv.getTag()); 115 | tv.setPaintFlags(tv.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); 116 | tv.setTextColor(Color.MAGENTA); 117 | } 118 | } 119 | }); 120 | } 121 | 122 | return row; 123 | } 124 | 125 | @Override 126 | public Filter getFilter() { 127 | if (mFilter == null) { 128 | mFilter = new CustomFilter(); 129 | } 130 | return mFilter; 131 | } 132 | 133 | /* Filter permissions by name, label or description based on contained text */ 134 | private class CustomFilter extends Filter { 135 | 136 | private boolean matches(CharSequence value, CharSequence filter) { 137 | return (value != null && value.toString().toLowerCase().contains(filter)); 138 | } 139 | 140 | @Override 141 | protected FilterResults performFiltering(CharSequence constraint) { 142 | FilterResults result = new FilterResults(); 143 | ArrayList items = new ArrayList(); 144 | if (constraint == null || constraint.length() == 0) { 145 | items.addAll(originalPermsList); 146 | } else { 147 | String findText = constraint.toString().toLowerCase(); 148 | PackageManager pm = context.getPackageManager(); 149 | for (PermissionInfo p : originalPermsList) { 150 | if (matches(p.name, findText) || matches(p.loadLabel(pm), findText) || matches(p.loadDescription(pm), findText)) { 151 | items.add(p); 152 | } 153 | } 154 | } 155 | result.values = items; 156 | result.count = items.size(); 157 | return result; 158 | } 159 | 160 | @Override 161 | protected void publishResults(CharSequence constraint, FilterResults results) { 162 | clear(); 163 | addAll((List) results.values); 164 | notifyDataSetChanged(); 165 | } 166 | 167 | } 168 | 169 | } 170 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phoenix09/XposedAppSettings/6f890b1160bee7aae5e7e8641f3930f3739ff973/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_menu_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phoenix09/XposedAppSettings/6f890b1160bee7aae5e7e8641f3930f3739ff973/app/src/main/res/drawable-hdpi/ic_menu_save.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phoenix09/XposedAppSettings/6f890b1160bee7aae5e7e8641f3930f3739ff973/app/src/main/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/ic_menu_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phoenix09/XposedAppSettings/6f890b1160bee7aae5e7e8641f3930f3739ff973/app/src/main/res/drawable-ldpi/ic_menu_save.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phoenix09/XposedAppSettings/6f890b1160bee7aae5e7e8641f3930f3739ff973/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_menu_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phoenix09/XposedAppSettings/6f890b1160bee7aae5e7e8641f3930f3739ff973/app/src/main/res/drawable-mdpi/ic_menu_save.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phoenix09/XposedAppSettings/6f890b1160bee7aae5e7e8641f3930f3739ff973/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_menu_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phoenix09/XposedAppSettings/6f890b1160bee7aae5e7e8641f3930f3739ff973/app/src/main/res/drawable-xhdpi/ic_menu_save.png -------------------------------------------------------------------------------- /app/src/main/res/layout/about.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 19 | 20 | 25 | 26 | 32 | 33 | 39 | 40 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/res/layout/app_list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 19 | 20 | 25 | 26 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/app_permission_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/app_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 18 | 19 | 25 | 26 | 33 | 34 | 35 | 39 | 40 | 44 | 45 | 49 | 50 | 54 | 55 | 56 | 64 | 65 | 66 | 70 | 71 | 72 | 80 | 81 | 82 |