├── README.md ├── app ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── aidl │ ├── android │ │ ├── app │ │ │ └── IApplicationThread.aidl │ │ └── content │ │ │ └── IIntentReceiver.aidl │ └── com │ │ └── tile │ │ └── screenoff │ │ └── IScreenOff.aidl │ ├── assets │ ├── 404.html │ ├── favicon.png │ ├── index.html │ └── starter.sh │ ├── java │ └── com │ │ └── tile │ │ └── screenoff │ │ ├── BinderContainer.java │ │ ├── DisplayControl.java │ │ ├── GlobalService.java │ │ ├── HttpRequest.java │ │ ├── HttpRequestParser.java │ │ ├── MainActivity.java │ │ ├── ScrOff.java │ │ ├── ScrOn.java │ │ ├── ScreenController.java │ │ ├── SimpleTcpServer.java │ │ └── tileService.java │ └── res │ ├── drawable │ ├── arrow.xml │ ├── fw.xml │ ├── icon.xml │ ├── selectground.xml │ └── tile.xml │ ├── layout │ └── main.xml │ ├── mipmap-anydpi-v26 │ └── icon.xml │ ├── mipmap │ └── ic.png │ ├── values-night-v31 │ └── colors.xml │ ├── values-night │ ├── colors.xml │ └── styles.xml │ ├── values-v31 │ └── colors.xml │ ├── values-zh-rTW │ └── strings.xml │ ├── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml │ ├── xml-v25 │ └── shortcuts.xml │ └── xml │ └── a.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── local.properties └── settings.gradle /README.md: -------------------------------------------------------------------------------- 1 | # ScreenOff 2 | An android app to control your Android's screen to display or not display via Shizuku. This is not the same with pressing power key, since other APPs will CONTINUE working after turning off the screen. 3 | # How it works 4 | By reflecting "android.view.SurfaceControl" (the same way with Scrcpy). Check it in "SurfaceControl.java". 5 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | namespace 'com.tile.screenoff' 7 | compileSdk 35 8 | 9 | defaultConfig { 10 | applicationId "com.tile.screenoff" 11 | minSdk 21 12 | targetSdk 35 13 | versionCode 21 14 | versionName "21" 15 | multiDexEnabled false 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | signingConfigs { 20 | release { 21 | storeFile file('D:\\key.keystore') // Keystore文件的路径 22 | storePassword 'wewewe' // Keystore密码 23 | keyAlias 'key0' // Key别名 24 | keyPassword 'wewewe' // Key密码 25 | } 26 | } 27 | buildTypes { 28 | release { 29 | shrinkResources false 30 | signingConfig signingConfigs.release 31 | minifyEnabled false 32 | } 33 | } 34 | compileOptions { 35 | sourceCompatibility JavaVersion.VERSION_1_8 36 | targetCompatibility JavaVersion.VERSION_1_8 37 | } 38 | lintOptions { 39 | checkReleaseBuilds false 40 | abortOnError false 41 | } 42 | buildFeatures { 43 | aidl true 44 | } 45 | } 46 | 47 | dependencies { 48 | 49 | implementation "dev.rikka.shizuku:api:13.1.0" 50 | implementation "dev.rikka.shizuku:provider:13.1.0" 51 | } -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 36 | 37 | 38 | 39 | 42 | 43 | 50 | 51 | 52 | 57 | 58 | 59 | 60 | 63 | 64 | 65 | 69 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/aidl/android/app/IApplicationThread.aidl: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | oneway interface IApplicationThread { 4 | } 5 | -------------------------------------------------------------------------------- /app/src/main/aidl/android/content/IIntentReceiver.aidl: -------------------------------------------------------------------------------- 1 | package android.content; 2 | 3 | oneway interface IIntentReceiver { 4 | } 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/aidl/com/tile/screenoff/IScreenOff.aidl: -------------------------------------------------------------------------------- 1 | package com.tile.screenoff; 2 | 3 | interface IScreenOff { 4 | 5 | void setPowerMode(boolean turnOff); 6 | 7 | void updateNowScreenState(boolean isScreenOn); 8 | 9 | int getNowScreenState(); 10 | 11 | void closeAndExit(); 12 | 13 | } -------------------------------------------------------------------------------- /app/src/main/assets/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 404 NOT FOUND 8 | 9 | 10 | 11 |
12 |

404 NOT FOUND!!!

13 |

Please try index.html

14 |
15 | 16 | -------------------------------------------------------------------------------- /app/src/main/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WuDi-ZhanShen/ScreenOff/354045d2625459d4d9932e687946507482e058d0/app/src/main/assets/favicon.png -------------------------------------------------------------------------------- /app/src/main/assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 37 | 息屏运行 38 | 80 | 81 | 82 | 83 |
84 |
85 |

您可以在此界面控制主机设备的亮灭屏。上方二维码就是本页面的链接。

86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
设备品牌{{brand}}
设备信息{{device}}
当前状态{{state}}
100 |
101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /app/src/main/assets/starter.sh: -------------------------------------------------------------------------------- 1 | pm grant com.tile.screenoff android.permission.WRITE_SECURE_SETTINGS 2 | 3 | file_name="ScreenController.dex" 4 | 5 | origin_path="$(dirname "$0")/$file_name" 6 | 7 | cache_dir="/data/local/tmp" 8 | target_path="$cache_dir/ScreenController.dex" 9 | 10 | if [[ -e $origin_path ]]; then 11 | cp -rf "$origin_path" $target_path 12 | export CLASSPATH="$target_path" 13 | nohup app_process /system/bin com.tile.screenoff.ScreenController > /dev/null 2>&1 & 14 | fi 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/tile/screenoff/BinderContainer.java: -------------------------------------------------------------------------------- 1 | package com.tile.screenoff; 2 | 3 | import android.os.IBinder; 4 | import android.os.Parcel; 5 | import android.os.Parcelable; 6 | 7 | public class BinderContainer implements Parcelable { // implements Parcelable 之后,这个类就可以作为intent的附加参数了。 8 | private final IBinder binder; 9 | 10 | public BinderContainer(IBinder binder) { 11 | this.binder = binder; 12 | } 13 | 14 | 15 | public IBinder getBinder() { //用这个函数来取出binder。其他函数都是自动生成的,只有这个函数是我自己写的。足以见得这个函数是多么的重要、多么的有技术含量 16 | return binder; 17 | } 18 | 19 | protected BinderContainer(Parcel in) { 20 | binder = in.readStrongBinder(); 21 | } 22 | 23 | public static final Creator CREATOR = new Creator() { 24 | @Override 25 | public BinderContainer createFromParcel(Parcel in) { 26 | return new BinderContainer(in); 27 | } 28 | 29 | @Override 30 | public BinderContainer[] newArray(int size) { 31 | return new BinderContainer[size]; 32 | } 33 | }; 34 | 35 | @Override 36 | public int describeContents() { 37 | return 0; 38 | } 39 | 40 | @Override 41 | public void writeToParcel(Parcel parcel, int i) { 42 | parcel.writeStrongBinder(binder); 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tile/screenoff/DisplayControl.java: -------------------------------------------------------------------------------- 1 | package com.tile.screenoff; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.os.IBinder; 5 | import android.util.Log; 6 | 7 | import java.lang.reflect.InvocationTargetException; 8 | import java.lang.reflect.Method; 9 | 10 | @SuppressLint({"PrivateApi", "SoonBlockedPrivateApi", "BlockedPrivateApi"}) 11 | //@TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 12 | public final class DisplayControl { 13 | 14 | private static final Class CLASS; 15 | 16 | static { 17 | Class displayControlClass = null; 18 | try { 19 | Class classLoaderFactoryClass = Class.forName("com.android.internal.os.ClassLoaderFactory"); 20 | Method createClassLoaderMethod = classLoaderFactoryClass.getDeclaredMethod("createClassLoader", String.class, String.class, String.class, 21 | ClassLoader.class, int.class, boolean.class, String.class); 22 | ClassLoader classLoader = (ClassLoader) createClassLoaderMethod.invoke(null, "/system/framework/services.jar", null, null, 23 | ClassLoader.getSystemClassLoader(), 0, true, null); 24 | 25 | displayControlClass = classLoader.loadClass("com.android.server.display.DisplayControl"); 26 | 27 | Method loadMethod = Runtime.class.getDeclaredMethod("loadLibrary0", Class.class, String.class); 28 | loadMethod.setAccessible(true); 29 | loadMethod.invoke(Runtime.getRuntime(), displayControlClass, "android_servers"); 30 | } catch (Throwable e) { 31 | Log.e("Could not initialize DisplayControl", e.getMessage()); 32 | // Do not throw an exception here, the methods will fail when they are called 33 | } 34 | CLASS = displayControlClass; 35 | } 36 | 37 | private static Method getPhysicalDisplayTokenMethod; 38 | private static Method getPhysicalDisplayIdsMethod; 39 | 40 | private DisplayControl() { 41 | // only static methods 42 | } 43 | 44 | private static Method getGetPhysicalDisplayTokenMethod() throws NoSuchMethodException { 45 | if (getPhysicalDisplayTokenMethod == null) { 46 | getPhysicalDisplayTokenMethod = CLASS.getMethod("getPhysicalDisplayToken", long.class); 47 | } 48 | return getPhysicalDisplayTokenMethod; 49 | } 50 | 51 | public static IBinder getPhysicalDisplayToken(long physicalDisplayId) { 52 | try { 53 | Method method = getGetPhysicalDisplayTokenMethod(); 54 | return (IBinder) method.invoke(null, physicalDisplayId); 55 | } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { 56 | Log.e("Could not invoke method", e.getMessage()); 57 | return null; 58 | } 59 | } 60 | 61 | private static Method getGetPhysicalDisplayIdsMethod() throws NoSuchMethodException { 62 | if (getPhysicalDisplayIdsMethod == null) { 63 | getPhysicalDisplayIdsMethod = CLASS.getMethod("getPhysicalDisplayIds"); 64 | } 65 | return getPhysicalDisplayIdsMethod; 66 | } 67 | 68 | public static long[] getPhysicalDisplayIds() { 69 | try { 70 | Method method = getGetPhysicalDisplayIdsMethod(); 71 | //return method; 72 | return (long[]) method.invoke(null); 73 | } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { 74 | Log.e("Could not invoke method", String.valueOf(e)); 75 | return null; 76 | } 77 | 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/tile/screenoff/GlobalService.java: -------------------------------------------------------------------------------- 1 | package com.tile.screenoff; 2 | 3 | import static java.lang.Math.abs; 4 | 5 | import android.accessibilityservice.AccessibilityService; 6 | import android.app.ActivityManager; 7 | import android.app.Service; 8 | import android.content.BroadcastReceiver; 9 | import android.content.Context; 10 | import android.content.Intent; 11 | import android.content.IntentFilter; 12 | import android.content.SharedPreferences; 13 | import android.content.res.Configuration; 14 | import android.graphics.PixelFormat; 15 | import android.graphics.drawable.ShapeDrawable; 16 | import android.graphics.drawable.shapes.OvalShape; 17 | import android.os.Build; 18 | import android.os.Handler; 19 | import android.os.IBinder; 20 | import android.os.RemoteException; 21 | import android.util.DisplayMetrics; 22 | import android.util.Log; 23 | import android.util.TypedValue; 24 | import android.view.KeyEvent; 25 | import android.view.MotionEvent; 26 | import android.view.OrientationEventListener; 27 | import android.view.View; 28 | import android.view.WindowManager; 29 | import android.view.accessibility.AccessibilityEvent; 30 | import android.widget.ImageView; 31 | 32 | import java.io.BufferedInputStream; 33 | import java.io.ByteArrayOutputStream; 34 | import java.io.IOException; 35 | import java.io.InputStream; 36 | import java.util.ArrayList; 37 | import java.util.List; 38 | import java.util.Locale; 39 | import java.util.Objects; 40 | 41 | import rikka.shizuku.Shizuku; 42 | 43 | public class GlobalService extends AccessibilityService implements SharedPreferences.OnSharedPreferenceChangeListener { 44 | 45 | 46 | private WindowManager windowManager; 47 | private WindowManager.LayoutParams params; 48 | private ImageView view; 49 | SharedPreferences sp; 50 | private boolean exist = false, canmove, doubleTap, shake, volume, netControl; 51 | int size, sensity, scrOnKey, scrOffKey; 52 | private int SCREEN_WIDTH, SCREEN_HEIGHT; 53 | OrientationEventListener listener; 54 | IScreenOff iScreenOff = null; 55 | 56 | 57 | public static boolean isScreenOffServiceRunning(Context context) { 58 | 59 | ActivityManager activityManager = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE); 60 | 61 | List runningServices = activityManager.getRunningServices(Integer.MAX_VALUE); 62 | if (runningServices.isEmpty()) { 63 | return false; 64 | } 65 | 66 | for (ActivityManager.RunningServiceInfo serviceInfo : runningServices) { 67 | // Log.d("TAG", "isGyroFixServiceRunning: "+serviceInfo.toString()); 68 | // Log.d("TAG", "GyroFixService.class.getName(): "+GyroFixService.class.getName()); 69 | // Log.d("TAG", "serviceInfo.service.getClassName(): "+serviceInfo.service.getClassName()); 70 | if (GlobalService.class.getName().equals(serviceInfo.service.getClassName())) { 71 | return true; 72 | } 73 | } 74 | return false; 75 | 76 | } 77 | 78 | final BroadcastReceiver myReceiver = new BroadcastReceiver() { 79 | 80 | @Override 81 | public void onReceive(Context context, Intent intent) { 82 | switch (intent.getAction()) { 83 | case "intent.screenoff.sendBinder": 84 | BinderContainer binderContainer = intent.getParcelableExtra("binder"); 85 | IBinder binder = binderContainer.getBinder(); 86 | //如果binder已经失去活性了,则不再继续解析 87 | if (!binder.pingBinder()) break; 88 | iScreenOff = IScreenOff.Stub.asInterface(binder); 89 | floatWindow(); 90 | break; 91 | case Intent.ACTION_SCREEN_OFF: 92 | try { 93 | iScreenOff.updateNowScreenState(false); 94 | } catch (RemoteException e) { 95 | e.printStackTrace(); 96 | } 97 | view.setKeepScreenOn(false); 98 | listener.disable(); 99 | if (exist) windowManager.updateViewLayout(view, params); 100 | break; 101 | case Intent.ACTION_SCREEN_ON: 102 | case Intent.ACTION_USER_PRESENT: 103 | try { 104 | iScreenOff.updateNowScreenState(true); 105 | } catch (RemoteException e) { 106 | e.printStackTrace(); 107 | } 108 | break; 109 | case "action.ScrOff": 110 | screenoff(intent.getBooleanExtra("state", true)); 111 | break; 112 | case "intent.screenoff.exit": 113 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 114 | disableSelf(); 115 | } else { 116 | stopSelf(); 117 | } 118 | break; 119 | } 120 | 121 | } 122 | }; 123 | 124 | @Override 125 | public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) { 126 | } 127 | 128 | @Override 129 | public void onInterrupt() { 130 | 131 | } 132 | 133 | 134 | @Override 135 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) { 136 | if (s.startsWith("x") || s.startsWith("y")) return; 137 | 138 | view.setVisibility(getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT && sharedPreferences.getBoolean("land", false) ? View.GONE : View.VISIBLE); 139 | size = (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, sharedPreferences.getInt("size", 50), getResources().getDisplayMetrics())); 140 | params.height = size; 141 | params.width = size; 142 | params.alpha = sharedPreferences.getInt("tran", 90) * 0.01f; 143 | canmove = sharedPreferences.getBoolean("canmove", true); 144 | doubleTap = sharedPreferences.getBoolean("doubleTap", false); 145 | shake = sharedPreferences.getBoolean("shake", false); 146 | sensity = sharedPreferences.getInt("sensity", 10); 147 | volume = sharedPreferences.getBoolean("volume", false); 148 | scrOnKey = sharedPreferences.getInt("scrOnKey", 24); 149 | scrOffKey = sharedPreferences.getInt("scrOffKey", 25); 150 | netControl = sharedPreferences.getBoolean("net", false); 151 | if (netControl) startServer(); 152 | floatWindow(); 153 | 154 | } 155 | 156 | 157 | @Override 158 | protected void onServiceConnected() { 159 | super.onServiceConnected(); 160 | sp = getSharedPreferences("s", 0); 161 | 162 | sensity = sp.getInt("sensity", 10); 163 | listener = new OrientationEventListener(this) { 164 | @Override 165 | public void onOrientationChanged(int orientation) { 166 | if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return; 167 | boolean wake = ((orientation >= 360 - sensity || orientation <= sensity) || orientation >= 90 - sensity && orientation <= 90 + sensity) || orientation >= 180 - sensity && orientation <= 180 + sensity || orientation >= 270 - sensity && orientation <= 270 + sensity; 168 | //下面是手机旋转准确角度与四个方向角度(0 90 180 270)的转换 169 | if (wake) { 170 | screenoff(false); 171 | this.disable(); 172 | } 173 | } 174 | 175 | }; 176 | windowManager = (WindowManager) getSystemService(Service.WINDOW_SERVICE); 177 | GetWidthHeight(); 178 | size = (int) (TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, sp.getInt("size", 50), getResources().getDisplayMetrics())); 179 | 180 | params = new WindowManager.LayoutParams(size, size, Build.VERSION.SDK_INT >= 22 ? WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY : WindowManager.LayoutParams.TYPE_SYSTEM_ERROR, 181 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | 182 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | 183 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | 184 | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, PixelFormat.RGBA_8888); 185 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) 186 | params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; 187 | params.alpha = sp.getInt("tran", 90) * 0.01f; 188 | boolean isLand = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; 189 | params.x = sp.getInt("x" + (isLand ? "1" : "2"), 0); 190 | params.y = sp.getInt("y" + (isLand ? "1" : "2"), 0); 191 | view = new ImageView(this); 192 | ShapeDrawable oval = new ShapeDrawable(new OvalShape()); 193 | oval.getPaint().setColor(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? getColor(R.color.bg) : 0xffc1e8ff); 194 | view.setBackground(oval); 195 | view.setImageResource(R.drawable.fw); 196 | canmove = sp.getBoolean("canmove", true); 197 | doubleTap = sp.getBoolean("doubleTap", false); 198 | shake = sp.getBoolean("shake", false); 199 | volume = sp.getBoolean("volume", false); 200 | scrOnKey = sp.getInt("scrOnKey", 24); 201 | scrOffKey = sp.getInt("scrOffKey", 25); 202 | netControl = sp.getBoolean("net", false); 203 | if (netControl) startServer(); 204 | view.setOnTouchListener(new View.OnTouchListener() { 205 | int lastX = 0; 206 | int lastY = 0; 207 | int paramX = 0; 208 | int paramY = 0; 209 | long lastDown = 0, lastUp = 0; 210 | boolean moved = false; 211 | 212 | @Override 213 | public boolean onTouch(View view, MotionEvent motionEvent) { 214 | 215 | switch (motionEvent.getAction()) { 216 | case MotionEvent.ACTION_DOWN: 217 | lastX = (int) motionEvent.getRawX(); 218 | lastY = (int) motionEvent.getRawY(); 219 | paramX = params.x; 220 | paramY = params.y; 221 | params.alpha = 1; 222 | new Handler().postDelayed(() -> { 223 | if (System.currentTimeMillis() - lastUp >= 400 && !moved) { 224 | screenoff(true); 225 | } 226 | }, 400); 227 | break; 228 | case MotionEvent.ACTION_MOVE: 229 | 230 | int dx = (int) motionEvent.getRawX() - lastX; 231 | int dy = (int) motionEvent.getRawY() - lastY; 232 | if (abs(dx) > 4 || abs(dy) > 4) 233 | moved = true; 234 | if (!canmove) return true; 235 | params.x = paramX + dx; 236 | params.y = paramY + dy; 237 | windowManager.updateViewLayout(view, params); 238 | 239 | break; 240 | case MotionEvent.ACTION_UP: 241 | lastUp = System.currentTimeMillis(); 242 | params.alpha = sp.getInt("tran", 90) * 0.01f; 243 | params.x = (params.x > (SCREEN_WIDTH - size) * 0.43) ? (SCREEN_WIDTH - size) / 2 : ((params.x < (SCREEN_WIDTH - size) * -0.43) ? -(SCREEN_WIDTH - size) / 2 : params.x); 244 | params.y = Math.min(Math.max(params.y, -(SCREEN_HEIGHT - size) / 2), (SCREEN_HEIGHT - size) / 2); 245 | windowManager.updateViewLayout(view, params); 246 | moved = false; 247 | boolean isLand = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; 248 | sp.edit().putInt("x" + (isLand ? "1" : "2"), params.x).putInt("y" + (isLand ? "1" : "2"), params.y).apply(); 249 | } 250 | 251 | 252 | if (!doubleTap) return false; 253 | switch (motionEvent.getAction()) { 254 | case MotionEvent.ACTION_DOWN: 255 | case MotionEvent.ACTION_OUTSIDE: 256 | if (System.currentTimeMillis() - lastDown <= 400) 257 | screenoff(false); 258 | lastDown = System.currentTimeMillis(); 259 | break; 260 | } 261 | return false; 262 | } 263 | }); 264 | view.setVisibility(getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT && sp.getBoolean("land", false) ? View.GONE : View.VISIBLE); 265 | floatWindow(); 266 | 267 | 268 | IntentFilter filter = new IntentFilter(); 269 | filter.addAction(Intent.ACTION_SCREEN_OFF); 270 | filter.addAction(Intent.ACTION_SCREEN_ON); 271 | filter.addAction(Intent.ACTION_USER_PRESENT); 272 | filter.addAction("action.ScrOff"); 273 | filter.addAction("intent.screenoff.sendBinder"); 274 | filter.addAction("intent.screenoff.exit"); 275 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 276 | registerReceiver(myReceiver, new IntentFilter("intent.screenoff.sendBinder"), RECEIVER_EXPORTED); 277 | } else { 278 | registerReceiver(myReceiver, new IntentFilter("intent.screenoff.sendBinder")); 279 | } 280 | sp.registerOnSharedPreferenceChangeListener(this); 281 | } 282 | 283 | 284 | void screenoff(Boolean bb) { 285 | try { 286 | if (iScreenOff.getNowScreenState() == 0) return; 287 | iScreenOff.setPowerMode(bb); 288 | view.setKeepScreenOn(bb); 289 | if (shake && bb) listener.enable(); 290 | } catch (RemoteException e) { 291 | e.printStackTrace(); 292 | } 293 | 294 | } 295 | 296 | 297 | @Override 298 | protected boolean onKeyEvent(KeyEvent event) { 299 | if (!volume || event.getAction() == KeyEvent.ACTION_UP) 300 | return super.onKeyEvent(event); 301 | 302 | try { 303 | final int keycode = event.getKeyCode(); 304 | final int nowState = iScreenOff.getNowScreenState(); 305 | if (keycode == scrOffKey && nowState == 1) { 306 | screenoff(true); 307 | return true; 308 | } 309 | if (keycode == scrOnKey && nowState == 2) { 310 | screenoff(false); 311 | return true; 312 | } 313 | } catch (RemoteException e) { 314 | e.printStackTrace(); 315 | } 316 | 317 | return super.onKeyEvent(event); 318 | } 319 | 320 | public void floatWindow() { 321 | if (sp.getBoolean("float", true)) { 322 | if (!exist) { 323 | windowManager.addView(view, params); 324 | exist = true; 325 | } else { 326 | windowManager.updateViewLayout(view, params); 327 | } 328 | } else { 329 | if (view != null) { 330 | try { 331 | windowManager.removeViewImmediate(view); 332 | } catch (Exception ignored) { 333 | } 334 | exist = false; 335 | } 336 | } 337 | } 338 | 339 | 340 | void GetWidthHeight() { 341 | DisplayMetrics metrics = new DisplayMetrics(); 342 | windowManager.getDefaultDisplay().getRealMetrics(metrics); 343 | SCREEN_WIDTH = metrics.widthPixels; 344 | SCREEN_HEIGHT = metrics.heightPixels; 345 | } 346 | 347 | @Override 348 | public void onConfigurationChanged(Configuration newConfig) { 349 | super.onConfigurationChanged(newConfig); 350 | boolean isLand = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT; 351 | if (sp.getBoolean("land", false)) 352 | view.setVisibility(isLand ? View.GONE : View.VISIBLE); 353 | GetWidthHeight(); 354 | params.x = sp.getInt("x" + (isLand ? "1" : "2"), 0); 355 | params.y = sp.getInt("y" + (isLand ? "1" : "2"), 0); 356 | ShapeDrawable oval = new ShapeDrawable(new OvalShape()); 357 | oval.getPaint().setColor(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? getColor(R.color.bg) : 0xffc1e8ff); 358 | view.setBackground(oval); 359 | view.setImageResource(R.drawable.fw); 360 | if (exist) windowManager.updateViewLayout(view, params); 361 | 362 | } 363 | 364 | @Override 365 | public void onDestroy() { 366 | unregisterReceiver(myReceiver); 367 | try { 368 | windowManager.removeViewImmediate(view); 369 | } catch (Exception ignored) { 370 | } 371 | exist = false; 372 | listener.disable(); 373 | sp.unregisterOnSharedPreferenceChangeListener(this); 374 | if (netControl) stopServer(); 375 | super.onDestroy(); 376 | } 377 | 378 | 379 | public static int port = 20000; 380 | private SimpleTcpServer server; 381 | 382 | private void startServer() { 383 | if (server != null) return; 384 | server = new SimpleTcpServer(new SimpleTcpServer.TcpConnectionListener() { 385 | private final HttpRequestParser parser = new HttpRequestParser(); 386 | 387 | @Override 388 | public void onReceive(final byte[] data) { 389 | parser.add(data); 390 | final HttpRequest request = parser.parse(); 391 | if (request != null) { 392 | output(request); 393 | parser.clear(); 394 | } 395 | } 396 | 397 | @Override 398 | public void onResponseSent() { 399 | server.restart(); 400 | } 401 | 402 | }, port); 403 | server.start(); 404 | 405 | } 406 | 407 | void stopServer() { 408 | server.stop(); 409 | server = null; 410 | } 411 | 412 | private void output(HttpRequest request) { 413 | if (server == null) { 414 | return; 415 | } 416 | String target = request.getRequestTarget(); 417 | Log.d("TAG", "output: " + target); 418 | if (target.equals("/") || target.equals("/index.html")) { 419 | outputHtml(buildIndexHtml(request), "200 OK"); 420 | } else if (target.equals("/favicon.ico")) { 421 | outputPng(Objects.requireNonNull(loadBinary("favicon.png"))); 422 | } else { 423 | try { 424 | switch (target.substring(0, 3)) { 425 | case "/1?": 426 | iScreenOff.setPowerMode(false); 427 | outputHtml("", "200 OK"); 428 | break; 429 | case "/2?": 430 | iScreenOff.setPowerMode(true); 431 | outputHtml("", "200 OK"); 432 | break; 433 | default: 434 | outputHtml(build404Html(), "404 Not Found"); 435 | break; 436 | } 437 | } catch (Exception ignored) { 438 | } 439 | 440 | } 441 | } 442 | 443 | private String buildIndexHtml(HttpRequest request) { 444 | String nowState = "未知"; 445 | try { 446 | switch (iScreenOff.getNowScreenState()) { 447 | case 0: 448 | nowState = "息屏"; 449 | break; 450 | case 1: 451 | nowState = "亮屏"; 452 | break; 453 | default: 454 | nowState = "息屏运行"; 455 | break; 456 | } 457 | } catch (RemoteException e) { 458 | e.printStackTrace(); 459 | } 460 | return Objects.requireNonNull(loadHtml("index.html")) 461 | .replace("{{brand}}", Build.BRAND) 462 | .replace("{{device}}", Build.MODEL + " Android " + Build.VERSION.RELEASE) 463 | .replace("{{state}}", nowState); 464 | } 465 | 466 | private String build404Html() { 467 | return loadHtml("404.html"); 468 | } 469 | 470 | private static final int BUFFER_SIZE = 1024 * 1024; 471 | private static final byte LF = 0x0a; 472 | private static final byte CR = 0x0d; 473 | 474 | private void outputHtml(String html, String responseCode) { 475 | String startLine = "HTTP/1.1 " + responseCode; 476 | List responseHeaders = new ArrayList<>(); 477 | responseHeaders.add("Content-Type: text/html; charset=UTF-8"); 478 | responseHeaders.add(String.format(Locale.getDefault(), "Content-Length: %d", html.getBytes().length)); 479 | StringBuilder builder = new StringBuilder(); 480 | builder.append(startLine).append(new String(new byte[]{CR, LF})); 481 | for (String responseHeader : responseHeaders) { 482 | builder.append(responseHeader).append(new String(new byte[]{CR, LF})); 483 | } 484 | builder.append(new String(new byte[]{CR, LF})); 485 | builder.append(html); 486 | server.output(builder.toString()); 487 | } 488 | 489 | private void outputPng(byte[] png) { 490 | String startLine = "HTTP/1.1 " + "200 OK"; 491 | List responseHeaders = new ArrayList<>(); 492 | responseHeaders.add("Content-Type: image/png"); 493 | responseHeaders.add(String.format(Locale.getDefault(), "Content-Length: %d", png.length)); 494 | StringBuilder builder = new StringBuilder(); 495 | builder.append(startLine).append(new String(new byte[]{CR, LF})); 496 | for (String responseHeader : responseHeaders) { 497 | builder.append(responseHeader).append(new String(new byte[]{CR, LF})); 498 | } 499 | builder.append(new String(new byte[]{CR, LF})); 500 | byte[] headerField = builder.toString().getBytes(); 501 | byte[] output = new byte[headerField.length + png.length]; 502 | System.arraycopy(headerField, 0, output, 0, headerField.length); 503 | System.arraycopy(png, 0, output, headerField.length, png.length); 504 | server.output(output); 505 | } 506 | 507 | private String loadHtml(String fileName) { 508 | byte[] binary = loadBinary(fileName); 509 | if (binary == null) { 510 | return null; 511 | } 512 | return new String(binary); 513 | } 514 | 515 | private byte[] loadBinary(String fileName) { 516 | try { 517 | InputStream is = getAssets().open(fileName); 518 | ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); 519 | byte[] chunk = new byte[BUFFER_SIZE]; 520 | BufferedInputStream bis = new BufferedInputStream(is, BUFFER_SIZE); 521 | try { 522 | int len; 523 | while ((len = bis.read(chunk, 0, BUFFER_SIZE)) > 0) { 524 | byteStream.write(chunk, 0, len); 525 | } 526 | return byteStream.toByteArray(); 527 | } finally { 528 | try { 529 | byteStream.reset(); 530 | bis.close(); 531 | } catch (Exception e) { 532 | e.printStackTrace(); 533 | } 534 | } 535 | } catch (IOException e) { 536 | e.printStackTrace(); 537 | } 538 | return null; 539 | } 540 | 541 | 542 | } 543 | -------------------------------------------------------------------------------- /app/src/main/java/com/tile/screenoff/HttpRequest.java: -------------------------------------------------------------------------------- 1 | package com.tile.screenoff; 2 | 3 | import java.util.Map; 4 | 5 | public class HttpRequest { 6 | private final StartLine startLine; 7 | private final Map headers; 8 | 9 | public HttpRequest(StartLine startLine, Map headers) { 10 | this.startLine = startLine; 11 | this.headers = headers; 12 | } 13 | 14 | public String getRequestTarget() { 15 | return startLine.requestTarget; 16 | } 17 | 18 | public Map getHeaders() { 19 | return headers; 20 | } 21 | 22 | public static class StartLine { 23 | private final String requestTarget; 24 | 25 | public StartLine(String requestTarget) { 26 | this.requestTarget = requestTarget; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/tile/screenoff/HttpRequestParser.java: -------------------------------------------------------------------------------- 1 | package com.tile.screenoff; 2 | 3 | import java.util.Arrays; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | public class HttpRequestParser { 8 | private static final byte SP = 0x20; 9 | private static final byte LF = 0x0a; 10 | private static final byte CR = 0x0d; 11 | 12 | private byte[] rawData; 13 | 14 | public HttpRequestParser() { 15 | rawData = new byte[]{}; 16 | } 17 | 18 | public void add(byte[] data) { 19 | byte[] newBytes = new byte[rawData.length + data.length]; 20 | System.arraycopy(rawData, 0, newBytes, 0, rawData.length); 21 | System.arraycopy(data, 0, newBytes, rawData.length, data.length); 22 | rawData = newBytes; 23 | } 24 | 25 | public void clear() { 26 | rawData = new byte[]{}; 27 | } 28 | 29 | public HttpRequest parse() { 30 | String[] lines = splitLines(rawData); 31 | if (lines == null || lines.length == 0) return null; 32 | HttpRequest.StartLine startLine = parseStartLine(lines[0]); 33 | if (startLine == null) return null; 34 | int emptyLineNum = findEmptyLine(lines); 35 | String[] headerField = Arrays.copyOfRange(lines, 1, emptyLineNum); 36 | Map headers = parseRequestHeader(headerField); 37 | return new HttpRequest(startLine, headers); 38 | } 39 | 40 | private HttpRequest.StartLine parseStartLine(String startLine) { 41 | if (startLine == null) { 42 | return null; 43 | } 44 | 45 | String[] splitLine = startLine.split(new String(new byte[]{SP})); 46 | if(splitLine.length != 3) { 47 | return null; 48 | } 49 | 50 | return new HttpRequest.StartLine(splitLine[1]); 51 | } 52 | 53 | private Map parseRequestHeader(String[] headerField) { 54 | if(headerField == null || headerField.length == 0) { 55 | return new HashMap<>(); 56 | } 57 | 58 | Map result = new HashMap<>(); 59 | 60 | for (String header : headerField) { 61 | String[] splitHeader = header.split(":", 2); 62 | if (splitHeader.length != 2) { 63 | continue; 64 | } 65 | result.put(splitHeader[0], splitHeader[1].trim()); 66 | } 67 | 68 | return result; 69 | } 70 | 71 | private String[] splitLines(byte[] rawData) { 72 | if (rawData == null || rawData.length == 0) { 73 | return null; 74 | } 75 | if (rawData[0] == CR || rawData[0] == LF) { 76 | return null; 77 | } 78 | 79 | String dataStr = new String(rawData); 80 | return dataStr.split(new String(new byte[]{CR, LF})); 81 | } 82 | 83 | private int findEmptyLine(String[] lines) { 84 | for (int i = 0; i < lines.length; i++) { 85 | if (lines[i].isEmpty()) { 86 | return i; 87 | } 88 | } 89 | return lines.length; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /app/src/main/java/com/tile/screenoff/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.tile.screenoff; 2 | 3 | import android.animation.LayoutTransition; 4 | import android.animation.ObjectAnimator; 5 | import android.app.Activity; 6 | import android.app.AlertDialog; 7 | import android.app.Service; 8 | import android.content.BroadcastReceiver; 9 | import android.content.ClipData; 10 | import android.content.ClipboardManager; 11 | import android.content.ComponentName; 12 | import android.content.Context; 13 | import android.content.Intent; 14 | import android.content.IntentFilter; 15 | import android.content.SharedPreferences; 16 | import android.content.pm.PackageManager; 17 | import android.content.res.Configuration; 18 | import android.graphics.Color; 19 | import android.graphics.drawable.ShapeDrawable; 20 | import android.graphics.drawable.shapes.RoundRectShape; 21 | import android.net.Uri; 22 | import android.os.Build; 23 | import android.os.Bundle; 24 | import android.os.IBinder; 25 | import android.os.PowerManager; 26 | import android.os.RemoteException; 27 | import android.provider.Settings; 28 | import android.text.Editable; 29 | import android.text.TextWatcher; 30 | import android.view.KeyEvent; 31 | import android.view.View; 32 | import android.view.Window; 33 | import android.view.WindowManager; 34 | import android.view.animation.AccelerateDecelerateInterpolator; 35 | import android.widget.Button; 36 | import android.widget.EditText; 37 | import android.widget.ImageView; 38 | import android.widget.LinearLayout; 39 | import android.widget.ScrollView; 40 | import android.widget.SeekBar; 41 | import android.widget.Switch; 42 | import android.widget.TextView; 43 | import android.widget.Toast; 44 | 45 | import java.io.DataOutputStream; 46 | import java.io.FileOutputStream; 47 | import java.io.IOException; 48 | import java.io.InputStream; 49 | import java.io.OutputStream; 50 | import java.net.Inet4Address; 51 | import java.net.InetAddress; 52 | import java.net.NetworkInterface; 53 | import java.util.Enumeration; 54 | import java.util.Locale; 55 | import java.util.regex.Pattern; 56 | import java.util.zip.ZipEntry; 57 | import java.util.zip.ZipFile; 58 | 59 | import rikka.shizuku.Shizuku; 60 | 61 | public class MainActivity extends Activity { 62 | private boolean isExpand = false, isServiceOK = false, isPermissionResultListenerRegistered = false; 63 | private int scrOffKey, scrOnKey; 64 | public IScreenOff iScreenOff = null; 65 | private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 66 | @Override 67 | public void onReceive(Context context, Intent intent) { 68 | BinderContainer binderContainer = intent.getParcelableExtra("binder"); 69 | IBinder binder = binderContainer.getBinder(); 70 | //如果binder已经失去活性了,则不再继续解析 71 | if (!binder.pingBinder()) return; 72 | iScreenOff = IScreenOff.Stub.asInterface(binder); 73 | enableScreenOffFunctions(); 74 | } 75 | }; 76 | 77 | 78 | @Override 79 | protected void onCreate(Bundle savedInstanceState) { 80 | Window window = getWindow(); 81 | window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); 82 | window.getAttributes().dimAmount = 0.5f; 83 | setContentView(R.layout.main); 84 | 85 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) 86 | window.setNavigationBarContrastEnforced(false); 87 | boolean isNight = (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_YES) == Configuration.UI_MODE_NIGHT_YES; 88 | window.setNavigationBarColor(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) ? Color.TRANSPARENT : getColor(isNight ? R.color.bgBlack : R.color.bgWhite) : (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) ? Color.TRANSPARENT : (isNight ? 0xff303034 : 0xffe4e2e6)); 89 | window.setStatusBarColor(Color.TRANSPARENT); 90 | 91 | SharedPreferences sp = getSharedPreferences("s", 0); 92 | if (sp.getBoolean("first", true)) { 93 | new AlertDialog.Builder(this) 94 | .setTitle(R.string.privacy) 95 | .setMessage(R.string.privacypolicy) 96 | .setNegativeButton(R.string.agree, (dialogInterface, i) -> { 97 | help(); 98 | sp.edit().putBoolean("first", false).apply(); 99 | }) 100 | .setCancelable(false) 101 | .setPositiveButton(R.string.disagree, (dialogInterface, i) -> finish()) 102 | .show(); 103 | 104 | 105 | } 106 | 107 | setButtonsOnclick(isNight, sp); 108 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 109 | registerReceiver(mBroadcastReceiver, new IntentFilter("intent.screenoff.sendBinder"), RECEIVER_EXPORTED); 110 | } else { 111 | registerReceiver(mBroadcastReceiver, new IntentFilter("intent.screenoff.sendBinder")); 112 | } 113 | super.onCreate(savedInstanceState); 114 | 115 | } 116 | 117 | 118 | private void showNet() { 119 | String[] i = new String[]{"wlan: ", "eth: ", "usb: ", "p2p: ", "lo: ", "unknown: "}; 120 | int i2; 121 | boolean avalible = false; 122 | try { 123 | Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); 124 | while (networkInterfaces.hasMoreElements()) { 125 | NetworkInterface nextElement = networkInterfaces.nextElement(); 126 | String name = nextElement.getName().toLowerCase(Locale.US); 127 | if (name.contains("wlan")) 128 | i2 = 0; 129 | else if (name.contains("eth")) 130 | i2 = 1; 131 | else if (name.contains("usb")) 132 | i2 = 2; 133 | else if (name.contains("p2p")) 134 | i2 = 3; 135 | else if (name.contains("lo")) 136 | i2 = 4; 137 | else 138 | i2 = 5; 139 | Enumeration inetAddresses = nextElement.getInetAddresses(); 140 | while (inetAddresses.hasMoreElements()) { 141 | InetAddress nextElement2 = inetAddresses.nextElement(); 142 | if (!nextElement2.isLoopbackAddress() && nextElement2 instanceof Inet4Address) { 143 | i[i2] += nextElement2.getHostAddress() + ":" + GlobalService.port + " "; 144 | avalible = true; 145 | } 146 | } 147 | } 148 | } catch (Exception ignored) { 149 | } 150 | int j = 0; 151 | StringBuilder sb = new StringBuilder(); 152 | while (j < 5) { 153 | if (Pattern.compile(": ").split(i[j]).length > 1) sb.append(i[j]); 154 | j++; 155 | } 156 | TextView textView = findViewById(R.id.title_text); 157 | textView.setOnClickListener(null); 158 | textView.setText(avalible ? sb.toString() : "no network avalible"); 159 | } 160 | 161 | private void setButtonsOnclick(boolean isNight, SharedPreferences sp) { 162 | 163 | if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { 164 | findViewById(R.id.left).setVisibility(View.VISIBLE); 165 | findViewById(R.id.right).setVisibility(View.VISIBLE); 166 | } 167 | LinearLayout linearLayout = findViewById(R.id.ll); 168 | EditText e1 = findViewById(R.id.e1); 169 | EditText e2 = findViewById(R.id.e2); 170 | Switch s1, s2, s3, s4, s5, s6, s7, s8; 171 | s1 = findViewById(R.id.s1); 172 | s2 = findViewById(R.id.s2); 173 | s3 = findViewById(R.id.s3); 174 | s4 = findViewById(R.id.s4); 175 | s5 = findViewById(R.id.s5); 176 | s6 = findViewById(R.id.s6); 177 | s7 = findViewById(R.id.s7); 178 | s8 = findViewById(R.id.s8); 179 | final String setting = Settings.Secure.getString(getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); 180 | s1.setChecked(setting != null && setting.contains(getPackageName())); 181 | s2.setChecked(sp.getBoolean("float", true)); 182 | s3.setChecked(sp.getBoolean("land", false)); 183 | s4.setChecked(!sp.getBoolean("canmove", true)); 184 | s5.setChecked(sp.getBoolean("doubleTap", false)); 185 | s6.setChecked(sp.getBoolean("shake", false)); 186 | s7.setChecked(sp.getBoolean("volume", false)); 187 | s8.setChecked(sp.getBoolean("net", false)); 188 | s3.setEnabled(s2.isChecked()); 189 | s4.setEnabled(s2.isChecked()); 190 | s5.setEnabled(s2.isChecked()); 191 | SeekBar sb = findViewById(R.id.sb); 192 | sb.setProgress(sp.getInt("size", 50)); 193 | EditText eb = findViewById(R.id.eb); 194 | eb.setText(String.valueOf(sp.getInt("size", 50))); 195 | SeekBar sc = findViewById(R.id.sc); 196 | sc.setProgress(sp.getInt("tran", 90)); 197 | EditText ec = findViewById(R.id.ec); 198 | ec.setText(String.valueOf(sp.getInt("tran", 90))); 199 | SeekBar sd = findViewById(R.id.sd); 200 | sd.setProgress(sp.getInt("sensity", 10)); 201 | EditText ed = findViewById(R.id.ed); 202 | ed.setText(String.valueOf(sp.getInt("sensity", 10))); 203 | sb.setEnabled(s2.isChecked()); 204 | sc.setEnabled(s2.isChecked()); 205 | sd.setEnabled(s2.isChecked()); 206 | s1.setOnCheckedChangeListener((compoundButton, isChecked) -> { 207 | 208 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !((PowerManager) getSystemService(Service.POWER_SERVICE)).isIgnoringBatteryOptimizations(getPackageName())) 209 | startActivity(new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, Uri.parse("package:" + getPackageName()))); 210 | 211 | if (!isServiceOK) { 212 | compoundButton.setChecked(false); 213 | Toast.makeText(MainActivity.this, R.string.active_first, Toast.LENGTH_SHORT).show(); 214 | return; 215 | } 216 | if (isChecked) { 217 | final String serviceName = new ComponentName(getPackageName(), GlobalService.class.getName()).flattenToString(); 218 | final String oldSetting = Settings.Secure.getString(getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); 219 | final String newSetting = oldSetting == null ? serviceName : serviceName + ":" + oldSetting; 220 | try { 221 | Settings.Secure.putInt(getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED, 1); 222 | Settings.Secure.putString(getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, newSetting); 223 | } catch (Exception e) { 224 | compoundButton.setChecked(false); 225 | Toast.makeText(MainActivity.this, R.string.mannually_open, Toast.LENGTH_SHORT).show(); 226 | Bundle bundle = new Bundle(); 227 | bundle.putString(":settings:fragment_args_key", serviceName); 228 | startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS).putExtra(":settings:fragment_args_key", serviceName).putExtra(":settings:show_fragment_args", bundle)); 229 | } 230 | if (s8.isChecked()) showNet(); 231 | } else { 232 | ((TextView) findViewById(R.id.title_text)).setText(R.string.shortcutoff); 233 | sendBroadcast(new Intent("intent.screenoff.exit")); 234 | } 235 | 236 | }); 237 | s2.setOnCheckedChangeListener((compoundButton, b) -> { 238 | sp.edit().putBoolean("float", b).apply(); 239 | s3.setEnabled(b); 240 | s4.setEnabled(b); 241 | s5.setEnabled(b); 242 | sb.setEnabled(b); 243 | sc.setEnabled(b); 244 | sd.setEnabled(b); 245 | }); 246 | s3.setOnCheckedChangeListener((compoundButton, b) -> sp.edit().putBoolean("land", b).apply()); 247 | s4.setOnCheckedChangeListener((compoundButton, b) -> sp.edit().putBoolean("canmove", !b).apply()); 248 | s5.setOnCheckedChangeListener((compoundButton, b) -> sp.edit().putBoolean("doubleTap", b).apply()); 249 | s6.setOnCheckedChangeListener((compoundButton, b) -> sp.edit().putBoolean("shake", b).apply()); 250 | s7.setOnCheckedChangeListener((compoundButton, b) -> { 251 | sp.edit().putBoolean("volume", b).apply(); 252 | e1.setEnabled(b); 253 | e2.setEnabled(b); 254 | }); 255 | s8.setOnCheckedChangeListener((compoundButton, b) -> { 256 | if (s1.isChecked()) { 257 | if (b) showNet(); 258 | else ((TextView) findViewById(R.id.title_text)).setText(R.string.shortcutoff); 259 | } 260 | sp.edit().putBoolean("net", b).apply(); 261 | }); 262 | if (s1.isChecked() && s8.isChecked()) showNet(); 263 | sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 264 | @Override 265 | public void onProgressChanged(SeekBar seekBar, int i, boolean b) { 266 | sp.edit().putInt("size", i).apply(); 267 | eb.setText(String.valueOf(i)); 268 | } 269 | 270 | @Override 271 | public void onStartTrackingTouch(SeekBar seekBar) { 272 | } 273 | 274 | @Override 275 | public void onStopTrackingTouch(SeekBar seekBar) { 276 | } 277 | }); 278 | eb.setOnKeyListener((view, i, keyEvent) -> { 279 | if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_ENTER && keyEvent.getAction() == KeyEvent.ACTION_DOWN && eb.getText().length() > 0) { 280 | int value = Integer.parseInt(eb.getText().toString()); 281 | if (value >= 0 && value <= 100) { 282 | sp.edit().putInt("size", value).apply(); 283 | sb.setProgress(value); 284 | } 285 | } 286 | return false; 287 | }); 288 | 289 | sc.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 290 | @Override 291 | public void onProgressChanged(SeekBar seekBar, int i, boolean b) { 292 | sp.edit().putInt("tran", i).apply(); 293 | ec.setText(String.valueOf(i)); 294 | } 295 | 296 | @Override 297 | public void onStartTrackingTouch(SeekBar seekBar) { 298 | 299 | } 300 | 301 | @Override 302 | public void onStopTrackingTouch(SeekBar seekBar) { 303 | } 304 | }); 305 | ec.setOnKeyListener((view, i, keyEvent) -> { 306 | if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_ENTER && keyEvent.getAction() == KeyEvent.ACTION_DOWN && ec.getText().length() > 0) { 307 | int value = Integer.parseInt(ec.getText().toString()); 308 | if (value >= 0 && value <= 100) { 309 | sp.edit().putInt("tran", value).apply(); 310 | sc.setProgress(value); 311 | } 312 | } 313 | return false; 314 | }); 315 | 316 | sd.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 317 | @Override 318 | public void onProgressChanged(SeekBar seekBar, int i, boolean b) { 319 | sp.edit().putInt("sensity", i).apply(); 320 | ed.setText(String.valueOf(i)); 321 | } 322 | 323 | @Override 324 | public void onStartTrackingTouch(SeekBar seekBar) { 325 | 326 | } 327 | 328 | @Override 329 | public void onStopTrackingTouch(SeekBar seekBar) { 330 | if (seekBar.getProgress() < 1) { 331 | seekBar.setProgress(1); 332 | Toast.makeText(MainActivity.this, R.string.toosmall, Toast.LENGTH_SHORT).show(); 333 | } 334 | 335 | } 336 | }); 337 | ed.setOnKeyListener((view, i, keyEvent) -> { 338 | if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_ENTER && keyEvent.getAction() == KeyEvent.ACTION_DOWN && ed.getText().length() > 0) { 339 | int value = Integer.parseInt(ed.getText().toString()); 340 | if (value >= 0 && value <= 30) { 341 | sp.edit().putInt("sensity", value).apply(); 342 | sd.setProgress(value); 343 | } 344 | } 345 | return false; 346 | }); 347 | e1.setEnabled(s7.isChecked()); 348 | e2.setEnabled(s7.isChecked()); 349 | scrOffKey = sp.getInt("scrOffKey", 25); 350 | scrOnKey = sp.getInt("scrOnKey", 24); 351 | e1.setText(String.valueOf(scrOffKey)); 352 | e2.setText(String.valueOf(scrOnKey)); 353 | e1.addTextChangedListener(new TextWatcher() { 354 | @Override 355 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { 356 | } 357 | 358 | @Override 359 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { 360 | if (charSequence.length() > 0) { 361 | scrOffKey = Integer.parseInt(String.valueOf(charSequence)); 362 | sp.edit().putInt("scrOffKey", scrOffKey).apply(); 363 | } 364 | } 365 | 366 | @Override 367 | public void afterTextChanged(Editable editable) { 368 | } 369 | }); 370 | e2.addTextChangedListener(new TextWatcher() { 371 | @Override 372 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { 373 | } 374 | 375 | @Override 376 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { 377 | if (charSequence.length() > 0) { 378 | scrOnKey = Integer.parseInt(String.valueOf(charSequence)); 379 | sp.edit().putInt("scrOnKey", scrOnKey).apply(); 380 | } 381 | } 382 | 383 | @Override 384 | public void afterTextChanged(Editable editable) { 385 | } 386 | }); 387 | findViewById(R.id.title_text).setOnClickListener(view -> help()); 388 | float density = getResources().getDisplayMetrics().density; 389 | findViewById(R.id.activate_button).setOnClickListener(view -> showActivate()); 390 | ShapeDrawable oval = new ShapeDrawable(new RoundRectShape(new float[]{30 * density, 30 * density, 30 * density, 30 * density, 0, 0, 0, 0}, null, null)); 391 | oval.getPaint().setColor(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? getColor(isNight ? R.color.bgBlack : R.color.bgWhite) : (isNight ? 0xff303034 : 0xffe4e2e6)); 392 | linearLayout.setBackground(oval); 393 | LayoutTransition transition = new LayoutTransition(); 394 | transition.setDuration(400L); 395 | ObjectAnimator animator = ObjectAnimator.ofFloat(null, "scaleX", 0.0f, 1.0f); 396 | transition.setAnimator(2, animator); 397 | linearLayout.setLayoutTransition(transition); 398 | ScrollView scrollView = findViewById(R.id.sv); 399 | Switch aSwitch = findViewById(R.id.screenoff_switch); 400 | aSwitch.setOnCheckedChangeListener((compoundButton, b) -> 401 | { 402 | if (!isServiceOK) return; 403 | try { 404 | iScreenOff.setPowerMode(!b); 405 | } catch (RemoteException e) { 406 | e.printStackTrace(); 407 | } 408 | }); 409 | ImageView imageView = findViewById(R.id.iv); 410 | final View.OnClickListener onClickListener = view -> { 411 | if (isExpand) { 412 | linearLayout.removeView(scrollView); 413 | ObjectAnimator a2 = ObjectAnimator.ofFloat(imageView, "rotation", 180f, 360f); 414 | a2.setDuration(800).setInterpolator(new AccelerateDecelerateInterpolator()); 415 | a2.start(); 416 | } else { 417 | linearLayout.addView(scrollView); 418 | ObjectAnimator a2 = ObjectAnimator.ofFloat(imageView, "rotation", 0f, 180f); 419 | a2.setDuration(800).setInterpolator(new AccelerateDecelerateInterpolator()); 420 | a2.start(); 421 | } 422 | isExpand = !isExpand; 423 | }; 424 | imageView.setOnClickListener(onClickListener); 425 | linearLayout.setOnClickListener(onClickListener); 426 | findViewById(R.id.lll).setOnClickListener(onClickListener); 427 | linearLayout.removeView(scrollView); 428 | 429 | } 430 | 431 | 432 | public void enableScreenOffFunctions() { 433 | Button button = findViewById(R.id.activate_button); 434 | isServiceOK = true; 435 | button.setText(getString(R.string.all_ok)); 436 | button.setTextColor(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? getColor(R.color.right) : 0x00000000); 437 | button.setOnClickListener(null); 438 | button.setOnLongClickListener(view -> { 439 | try { 440 | sendBroadcast(new Intent("intent.screenoff.exit")); 441 | iScreenOff.closeAndExit(); 442 | } catch (RemoteException e) { 443 | e.printStackTrace(); 444 | } 445 | Toast.makeText(this, R.string.service_closed, Toast.LENGTH_SHORT).show(); 446 | finish(); 447 | return false; 448 | }); 449 | Switch aSwitch = findViewById(R.id.screenoff_switch); 450 | aSwitch.setEnabled(true); 451 | } 452 | 453 | @Override 454 | public void onBackPressed() { 455 | if (isExpand) { 456 | findViewById(R.id.iv).performClick(); 457 | } else 458 | finish(); 459 | } 460 | 461 | @Override 462 | public boolean onKeyDown(int keyCode, KeyEvent event) { 463 | super.onKeyDown(keyCode, event); 464 | if (isExpand) { 465 | Toast.makeText(this, String.format(Locale.getDefault(), getString(R.string.key_pressed), KeyEvent.keyCodeToString(keyCode).replace("KEYCODE_", ""), keyCode), Toast.LENGTH_SHORT).show(); 466 | return true; 467 | } 468 | if (!isServiceOK) return true; 469 | Switch aSwitch = findViewById(R.id.screenoff_switch); 470 | if (keyCode == scrOffKey) aSwitch.setChecked(true); 471 | if (keyCode == scrOnKey) aSwitch.setChecked(false); 472 | return true; 473 | } 474 | 475 | 476 | private final Shizuku.OnRequestPermissionResultListener RL = (requestCode, grantResult) -> check(); 477 | 478 | 479 | //检查Shizuku权限,申请Shizuku权限的函数 480 | private void check() { 481 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return; 482 | 483 | if (!isPermissionResultListenerRegistered) { 484 | Shizuku.addRequestPermissionResultListener(RL); 485 | isPermissionResultListenerRegistered = true; 486 | } 487 | boolean b = true, c = false; 488 | try { 489 | if (Shizuku.checkSelfPermission() != PackageManager.PERMISSION_GRANTED) 490 | Shizuku.requestPermission(0); 491 | else c = true; 492 | } catch (Exception e) { 493 | if (checkSelfPermission("moe.shizuku.manager.permission.API_V23") == PackageManager.PERMISSION_GRANTED) 494 | c = true; 495 | if (e.getClass() == IllegalStateException.class) { 496 | b = false; 497 | Toast.makeText(this, "shizuku未运行", Toast.LENGTH_SHORT).show(); 498 | } 499 | } 500 | if (b && c) { 501 | try { 502 | Process p = Shizuku.newProcess(new String[]{"sh"}, null, null); 503 | OutputStream out = p.getOutputStream(); 504 | out.write(("sh " + getExternalFilesDir(null).getPath() + "/starter.sh\nexit\n").getBytes()); 505 | out.flush(); 506 | out.close(); 507 | } catch (IOException ioException) { 508 | Toast.makeText(this, "激活失败", Toast.LENGTH_SHORT).show(); 509 | } 510 | } 511 | 512 | } 513 | 514 | @Override 515 | public void onConfigurationChanged(Configuration newConfig) { 516 | if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { 517 | findViewById(R.id.left).setVisibility(View.VISIBLE); 518 | findViewById(R.id.right).setVisibility(View.VISIBLE); 519 | } else { 520 | findViewById(R.id.left).setVisibility(View.GONE); 521 | findViewById(R.id.right).setVisibility(View.GONE); 522 | } 523 | super.onConfigurationChanged(newConfig); 524 | } 525 | 526 | //一些收尾工作,取消注册监听器什么的 527 | @Override 528 | protected void onDestroy() { 529 | if (isPermissionResultListenerRegistered) Shizuku.removeRequestPermissionResultListener(RL); 530 | unregisterReceiver(mBroadcastReceiver); 531 | super.onDestroy(); 532 | } 533 | 534 | public void finish(View view) { 535 | finish(); 536 | } 537 | 538 | public void help() { 539 | new AlertDialog.Builder(this) 540 | .setTitle(R.string.help_title) 541 | .setMessage(R.string.help_conntent) 542 | .setNegativeButton(R.string.understand, null) 543 | .show(); 544 | } 545 | 546 | public void showActivate() { 547 | unzipFiles(); 548 | final String command = "sh " + getExternalFilesDir(null).getPath() + "/starter.sh"; 549 | AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this) 550 | .setMessage(String.format(getString(R.string.active_steps), command)) 551 | .setTitle(R.string.need_active) 552 | .setNeutralButton(R.string.copy_cmd, (dialogInterface, i) -> { 553 | ((ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("c", "adb shell " + command)); 554 | Toast.makeText(MainActivity.this, String.format(getString(R.string.cmd_copy_finish), command), Toast.LENGTH_SHORT).show(); 555 | }) 556 | .setNegativeButton(R.string.by_root, (dialoginterface, i) -> { 557 | Process p; 558 | try { 559 | p = Runtime.getRuntime().exec("su"); 560 | DataOutputStream o = new DataOutputStream(p.getOutputStream()); 561 | o.writeBytes(command); 562 | o.flush(); 563 | o.close(); 564 | } catch (IOException ignored) { 565 | Toast.makeText(MainActivity.this, R.string.active_failed, Toast.LENGTH_SHORT).show(); 566 | } 567 | }); 568 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) 569 | builder.setPositiveButton(R.string.by_shizuku, (dialogInterface, i) -> check()); 570 | builder.show(); 571 | } 572 | 573 | private void unzipFiles() { 574 | 575 | String file1 = getExternalFilesDir(null).getPath() + "/starter.sh"; 576 | try { 577 | InputStream is = getAssets().open("starter.sh"); 578 | FileOutputStream fileOutputStream = new FileOutputStream(file1); 579 | byte[] buffer = new byte[1024]; 580 | int byteRead; 581 | while (-1 != (byteRead = is.read(buffer))) { 582 | fileOutputStream.write(buffer, 0, byteRead); 583 | } 584 | is.close(); 585 | fileOutputStream.flush(); 586 | fileOutputStream.close(); 587 | } catch (IOException ignored) { 588 | } 589 | String file2 = getExternalFilesDir(null).getPath() + "/ScreenController.dex"; 590 | try { 591 | ZipFile zipFile = new ZipFile(getPackageResourcePath()); 592 | // 遍历zip文件中的所有条目 593 | Enumeration entries = zipFile.entries(); 594 | while (entries.hasMoreElements()) { 595 | ZipEntry entry = entries.nextElement(); 596 | 597 | // 如果条目名称为classes.dex,则解压该条目到指定目录 598 | if (entry.getName().equals("classes.dex")) { 599 | InputStream inputStream = zipFile.getInputStream(entry); 600 | FileOutputStream fos = new FileOutputStream(file2); 601 | byte[] buffer = new byte[1024]; 602 | int len; 603 | while ((len = inputStream.read(buffer)) > 0) { 604 | fos.write(buffer, 0, len); 605 | } 606 | fos.close(); 607 | break; 608 | } 609 | } 610 | 611 | // 关闭ZipFile对象 612 | zipFile.close(); 613 | } catch (IOException e) { 614 | e.printStackTrace(); 615 | } 616 | } 617 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tile/screenoff/ScrOff.java: -------------------------------------------------------------------------------- 1 | package com.tile.screenoff; 2 | 3 | import android.app.Activity; 4 | import android.content.ComponentName; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.provider.Settings; 8 | 9 | public class ScrOff extends Activity { 10 | 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | setTheme(android.R.style.Theme_NoDisplay); 15 | super.onCreate(savedInstanceState); 16 | final String setting = Settings.Secure.getString(getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); 17 | if (setting != null && setting.contains(getPackageName())) { 18 | sendBroadcast(new Intent("action.ScrOff").putExtra("state", true)); 19 | } else { 20 | final String serviceName = new ComponentName(getPackageName(), GlobalService.class.getName()).flattenToString(); 21 | final String oldSetting = Settings.Secure.getString(getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); 22 | final String newSetting = oldSetting == null ? serviceName : serviceName + ":" + oldSetting; 23 | try { 24 | Settings.Secure.putInt(getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED, 1); 25 | Settings.Secure.putString(getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, newSetting); 26 | } catch (Exception e) { 27 | Bundle bundle = new Bundle(); 28 | bundle.putString(":settings:fragment_args_key", serviceName); 29 | startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS).putExtra(":settings:fragment_args_key", serviceName).putExtra(":settings:show_fragment_args", bundle)); 30 | } 31 | } 32 | } 33 | 34 | @Override 35 | protected void onResume() { 36 | finish(); 37 | super.onResume(); 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tile/screenoff/ScrOn.java: -------------------------------------------------------------------------------- 1 | package com.tile.screenoff; 2 | 3 | import android.app.Activity; 4 | import android.content.ComponentName; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.provider.Settings; 8 | 9 | public class ScrOn extends Activity { 10 | 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | setTheme(android.R.style.Theme_NoDisplay); 15 | super.onCreate(savedInstanceState); 16 | final String setting = Settings.Secure.getString(getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); 17 | if (setting != null && setting.contains(getPackageName())) { 18 | sendBroadcast(new Intent("action.ScrOff").putExtra("state", false)); 19 | } else { 20 | final String serviceName = new ComponentName(getPackageName(), GlobalService.class.getName()).flattenToString(); 21 | final String oldSetting = Settings.Secure.getString(getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); 22 | final String newSetting = oldSetting == null ? serviceName : serviceName + ":" + oldSetting; 23 | try { 24 | Settings.Secure.putInt(getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED, 1); 25 | Settings.Secure.putString(getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, newSetting); 26 | } catch (Exception e) { 27 | Bundle bundle = new Bundle(); 28 | bundle.putString(":settings:fragment_args_key", serviceName); 29 | startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS).putExtra(":settings:fragment_args_key", serviceName).putExtra(":settings:show_fragment_args", bundle)); 30 | } 31 | } 32 | } 33 | 34 | @Override 35 | protected void onResume() { 36 | finish(); 37 | super.onResume(); 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tile/screenoff/ScreenController.java: -------------------------------------------------------------------------------- 1 | package com.tile.screenoff; 2 | 3 | import android.app.IApplicationThread; 4 | import android.content.IIntentReceiver; 5 | import android.content.Intent; 6 | import android.os.Build; 7 | import android.os.Bundle; 8 | import android.os.Handler; 9 | import android.os.IBinder; 10 | import android.os.Looper; 11 | import android.os.RemoteException; 12 | import android.util.Log; 13 | 14 | import java.io.BufferedReader; 15 | import java.io.InputStreamReader; 16 | import java.lang.reflect.InvocationTargetException; 17 | import java.lang.reflect.Method; 18 | import java.util.Scanner; 19 | 20 | public class ScreenController { 21 | 22 | 23 | //发送广播需要的三个东西 24 | static boolean isBroadcastSent = false; 25 | static Intent intent = null; 26 | static Object iActivityManagerObj = null; 27 | 28 | 29 | public enum ScreenState { 30 | STATE_OFF, STATE_ON, STATE_SPECIAL 31 | } 32 | 33 | static ScreenState screenState = ScreenState.STATE_ON; 34 | 35 | 36 | private static final Class CLASS; 37 | public static final int POWER_MODE_OFF = 0; 38 | public static final int POWER_MODE_NORMAL = 2; 39 | 40 | static { 41 | try { 42 | CLASS = Class.forName("android.view.SurfaceControl"); 43 | } catch (ClassNotFoundException e) { 44 | throw new AssertionError(e); 45 | } 46 | } 47 | public static boolean useDisplayControl = 48 | Build.VERSION.SDK_INT >= 34; 49 | private static Method getBuiltInDisplayMethod; 50 | private static Method setDisplayPowerModeMethod; 51 | private static boolean listenVolumeKey = false; 52 | static Thread h1; 53 | static Process listenVolumeKeyProcess; 54 | 55 | private static Method getGetBuiltInDisplayMethod() throws NoSuchMethodException { 56 | if (getBuiltInDisplayMethod == null) 57 | getBuiltInDisplayMethod = Build.VERSION.SDK_INT < Build.VERSION_CODES.Q ? CLASS.getMethod("getBuiltInDisplay", int.class) : CLASS.getMethod("getInternalDisplayToken"); 58 | return getBuiltInDisplayMethod; 59 | } 60 | 61 | public static IBinder getBuiltInDisplay() { 62 | try { 63 | // Change the power mode for all physical displays 64 | Log.e("getBuiltInDisplay","Android 14" + useDisplayControl); 65 | if (useDisplayControl) { 66 | long[] physicalDisplayIds = DisplayControl.getPhysicalDisplayIds(); 67 | if (physicalDisplayIds == null) { 68 | Log.e("getBuiltInDisplay", "Could not get physical display ids"); 69 | return null; 70 | } 71 | 72 | for (long physicalDisplayId : physicalDisplayIds) { 73 | return DisplayControl.getPhysicalDisplayToken( 74 | physicalDisplayId); 75 | 76 | } 77 | } else { 78 | Method method = getGetBuiltInDisplayMethod(); 79 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { 80 | // call getBuiltInDisplay(0) 81 | return (IBinder) method.invoke(null, 0); 82 | } 83 | 84 | // call getInternalDisplayToken() 85 | return (IBinder) method.invoke(null); 86 | } 87 | } catch(InvocationTargetException | IllegalAccessException | NoSuchMethodException e){ 88 | Log.e("Could not invoke method", String.valueOf(e)); 89 | return null; 90 | } 91 | return null; 92 | } 93 | 94 | private static Method getSetDisplayPowerModeMethod() throws NoSuchMethodException { 95 | if (setDisplayPowerModeMethod == null) 96 | setDisplayPowerModeMethod = CLASS.getMethod("setDisplayPowerMode", IBinder.class, int.class); 97 | return setDisplayPowerModeMethod; 98 | } 99 | 100 | public static void setDisplayPowerMode(IBinder displayToken, int mode) { 101 | try { 102 | Method method = getSetDisplayPowerModeMethod(); 103 | method.invoke(null, displayToken, mode); 104 | } catch (InvocationTargetException | IllegalAccessException | 105 | NoSuchMethodException ignored) { 106 | } 107 | } 108 | 109 | public static void main(String[] args) { 110 | 111 | if (Looper.getMainLooper() == null) { 112 | Looper.prepareMainLooper(); 113 | } 114 | 115 | Log.e("getBuiltInDisplay", "Android 14" + useDisplayControl); 116 | //检查权限 117 | int uid = android.os.Process.myUid(); 118 | if (uid != 0 && uid != 2000) { 119 | System.err.printf("Insufficient permission! Need to be launched by adb (uid 2000) or root (uid 0), but your uid is %d \n", uid); 120 | System.exit(-1); 121 | return; 122 | } 123 | 124 | System.out.println("Start ScreenController Service."); 125 | 126 | isBroadcastSent = sendBinderToAppByStickyBroadcast();//发送广播,将binder传给APP 127 | if (!isBroadcastSent) { 128 | System.err.println("Failed to send broadcast!"); 129 | System.exit(-1); 130 | return; 131 | } 132 | 133 | //加入JVM异常关闭时的处理程序 134 | Runtime.getRuntime().addShutdownHook(new Thread() { 135 | @Override 136 | public void run() { 137 | if (isBroadcastSent) isBroadcastSent = !removeStickyBroadcast(); 138 | } 139 | }); 140 | 141 | // try { 142 | // Scanner scanner = new Scanner(System.in); 143 | // //用来保持进程不退出,同时如果用户输入exit则程序退出 144 | // String inline; 145 | // while ((inline = scanner.nextLine()) != null) { 146 | // if (inline.equals("exit")) 147 | // break; 148 | // } 149 | // scanner.close(); 150 | // } catch (Exception unused) { 151 | // //用户使用nohup命令启动,scanner捕捉不到任何输入,会抛出异常。 152 | // while (true) ; 153 | // } 154 | 155 | Looper.loop(); 156 | 157 | 158 | if (isBroadcastSent) isBroadcastSent = !removeStickyBroadcast(); 159 | System.out.println("Stop ScreenController Service.\n"); 160 | System.exit(0); 161 | } 162 | 163 | private static void stopListenVolumeKey() { 164 | listenVolumeKey = false; 165 | if (Build.VERSION.SDK_INT >= 26) { 166 | listenVolumeKeyProcess.destroyForcibly(); 167 | } else { 168 | listenVolumeKeyProcess.destroy(); 169 | } 170 | new Handler().postDelayed(new Runnable() { 171 | @Override 172 | public void run() { 173 | h1.interrupt(); 174 | } 175 | }, 2000); 176 | } 177 | 178 | private static void startListenVolumeKey() { 179 | if (listenVolumeKey) return; 180 | listenVolumeKey = true; 181 | h1 = new Thread(new Runnable() { 182 | @Override 183 | public void run() { 184 | try { 185 | listenVolumeKeyProcess = Runtime.getRuntime().exec("getevent"); 186 | BufferedReader mReader = new BufferedReader(new InputStreamReader(listenVolumeKeyProcess.getInputStream())); 187 | String inline; 188 | while ((inline = mReader.readLine()) != null) { 189 | if (!listenVolumeKey) break; 190 | if (inline.endsWith("0000 0000 00000000")) continue; 191 | if (inline.endsWith("0001 0072 00000001") || inline.endsWith("0001 0073 00000001")) { 192 | 193 | setDisplayPowerMode(getBuiltInDisplay(), POWER_MODE_NORMAL); 194 | } 195 | } 196 | mReader.close(); 197 | listenVolumeKeyProcess.waitFor(); 198 | if (Build.VERSION.SDK_INT >= 26) { 199 | listenVolumeKeyProcess.destroyForcibly(); 200 | } else { 201 | listenVolumeKeyProcess.destroy(); 202 | } 203 | } catch (Exception ignored) { 204 | } 205 | } 206 | }); 207 | h1.start(); 208 | } 209 | 210 | private static boolean sendBinderToAppByStickyBroadcast() { 211 | 212 | try { 213 | //生成binder 214 | IBinder binder = new IScreenOff.Stub() { 215 | @Override 216 | public void setPowerMode(boolean turnOff) throws RemoteException { 217 | if (turnOff) { 218 | if (screenState == ScreenState.STATE_ON) { 219 | setDisplayPowerMode(getBuiltInDisplay(), POWER_MODE_OFF); 220 | // setDisplayPowerMode(getBuiltInDisplay(), POWER_MODE_NORMAL); 221 | screenState = ScreenState.STATE_SPECIAL; 222 | } 223 | } else { 224 | if (screenState == ScreenState.STATE_SPECIAL) { 225 | setDisplayPowerMode(getBuiltInDisplay(), POWER_MODE_NORMAL); 226 | screenState = ScreenState.STATE_ON; 227 | } 228 | } 229 | } 230 | 231 | @Override 232 | public int getNowScreenState() throws RemoteException { 233 | return screenState.ordinal(); 234 | } 235 | 236 | @Override 237 | public void updateNowScreenState(boolean isScreenOn) throws RemoteException { 238 | screenState = isScreenOn ? ScreenState.STATE_ON : ScreenState.STATE_OFF; 239 | } 240 | 241 | @Override 242 | public void closeAndExit() throws RemoteException { 243 | if (isBroadcastSent) isBroadcastSent = !removeStickyBroadcast(); 244 | System.out.println("Stop ScreenController Service.\n"); 245 | System.exit(0); 246 | } 247 | 248 | }; 249 | 250 | //把binder填到一个可以用Intent来传递的容器中 251 | BinderContainer binderContainer = new BinderContainer(binder); 252 | // 创建 Intent 对象,并将binder作为附加参数 253 | intent = new Intent("intent.screenoff.sendBinder"); 254 | intent.putExtra("binder", binderContainer); 255 | 256 | // 获取 IActivityManager 类 257 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 258 | iActivityManagerObj = Class.forName("android.app.IActivityManager$Stub").getMethod("asInterface", IBinder.class).invoke(null, Class.forName("android.os.ServiceManager").getDeclaredMethod("getService", String.class).invoke(null, "activity")); 259 | } else { 260 | Class activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative"); 261 | Method getDefaultMethod = activityManagerNativeClass.getMethod("getDefault"); 262 | iActivityManagerObj = getDefaultMethod.invoke(activityManagerNativeClass); 263 | } 264 | 265 | // 获取 broadcastIntent 方法 266 | Method broadcastIntentMethod = Class.forName("android.app.IActivityManager").getDeclaredMethod( 267 | "broadcastIntent", 268 | IApplicationThread.class, 269 | Intent.class, 270 | String.class, 271 | IIntentReceiver.class, 272 | int.class, 273 | String.class, 274 | Bundle.class, 275 | String[].class, 276 | int.class, 277 | Bundle.class, 278 | boolean.class, 279 | boolean.class, 280 | int.class 281 | ); 282 | // 调用 broadcastIntent 方法,发送粘性广播 283 | broadcastIntentMethod.invoke( 284 | iActivityManagerObj, 285 | null, 286 | intent, 287 | null, 288 | null, 289 | -1, 290 | null, 291 | null, 292 | null, 293 | 0, 294 | null, 295 | false, 296 | true, 297 | -1 298 | ); 299 | 300 | } catch (Exception e) { 301 | 302 | return false; 303 | } 304 | return true; 305 | } 306 | 307 | private static boolean removeStickyBroadcast() { 308 | if (intent == null || iActivityManagerObj == null) return true; 309 | 310 | try { 311 | // 获取 unbroadcastIntent 方法 312 | Method unbroadcastIntentMethod = Class.forName("android.app.IActivityManager").getDeclaredMethod("unbroadcastIntent", IApplicationThread.class, Intent.class, int.class); 313 | // 调用 broadcastIntent 方法,发送粘性广播 314 | unbroadcastIntentMethod.invoke(iActivityManagerObj, null, intent, -1); 315 | intent = null; 316 | iActivityManagerObj = null; 317 | } catch (Exception e) { 318 | System.err.println("Failed to remove broadcast!"); 319 | return false; 320 | } 321 | return true; 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /app/src/main/java/com/tile/screenoff/SimpleTcpServer.java: -------------------------------------------------------------------------------- 1 | package com.tile.screenoff; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.BufferedOutputStream; 5 | import java.io.IOException; 6 | import java.io.OutputStream; 7 | import java.net.InetSocketAddress; 8 | import java.net.ServerSocket; 9 | import java.net.Socket; 10 | import java.util.Arrays; 11 | 12 | public class SimpleTcpServer { 13 | 14 | public interface TcpConnectionListener { 15 | void onReceive(byte[] data); 16 | void onResponseSent(); 17 | } 18 | private ServerSocket serverSocket; 19 | private static final int CAPACITY = 1024 * 1024; 20 | 21 | private final TcpConnectionListener listener; 22 | private BufferedInputStream in; 23 | private OutputStream out; 24 | 25 | public SimpleTcpServer(TcpConnectionListener listener, int port) { 26 | this.listener = listener; 27 | try { 28 | serverSocket = new ServerSocket(); 29 | serverSocket.bind(new InetSocketAddress(port)); 30 | } catch (IOException e) { 31 | e.printStackTrace(); 32 | } 33 | } 34 | 35 | public void start() { 36 | if (serverSocket == null) { 37 | return; 38 | } 39 | 40 | new Thread(() -> { 41 | try { 42 | Socket socket = serverSocket.accept(); 43 | in = new BufferedInputStream(socket.getInputStream()); 44 | out = new BufferedOutputStream(socket.getOutputStream()); 45 | startInputThread(); 46 | } catch (IOException e) { 47 | e.printStackTrace(); 48 | } 49 | }).start(); 50 | } 51 | 52 | private void startInputThread() { 53 | new Thread(() -> { 54 | try { 55 | while(true) { 56 | byte[] buf = new byte[CAPACITY]; 57 | if (in == null) { 58 | break; 59 | } 60 | int size = in.read(buf); 61 | if (size > 0) { 62 | byte[] chunk = Arrays.copyOfRange(buf, 0, size); 63 | listener.onReceive(chunk); 64 | } else { 65 | restart(); 66 | break; 67 | } 68 | } 69 | } catch (IOException e) { 70 | restart(); 71 | } 72 | }).start(); 73 | } 74 | 75 | public void output(String data) { 76 | output(data.getBytes()); 77 | } 78 | 79 | public void output(final byte[] data) { 80 | new Thread(() -> { 81 | if (out != null) { 82 | try { 83 | out.write(data); 84 | out.flush(); 85 | listener.onResponseSent(); 86 | } catch (IOException e) { 87 | restart(); 88 | } 89 | } 90 | }).start(); 91 | } 92 | 93 | public void stop() { 94 | try { 95 | if (in != null) { 96 | in.close(); 97 | in = null; 98 | } 99 | if (out != null) { 100 | out.close(); 101 | out = null; 102 | } 103 | 104 | } catch (IOException e) { 105 | e.printStackTrace(); 106 | } finally { 107 | in = null; 108 | out = null; 109 | } 110 | } 111 | 112 | public void restart() { 113 | stop(); 114 | start(); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /app/src/main/java/com/tile/screenoff/tileService.java: -------------------------------------------------------------------------------- 1 | package com.tile.screenoff; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Intent; 5 | import android.os.Build; 6 | import android.service.quicksettings.TileService; 7 | 8 | @TargetApi(Build.VERSION_CODES.N) 9 | public class tileService extends TileService { 10 | 11 | @Override 12 | public void onClick() { 13 | if (getQsTile() == null) return; 14 | startActivityAndCollapse(new Intent(tileService.this,ScrOff.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 15 | super.onClick(); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/arrow.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/fw.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selectground.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tile.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 26 | 27 | 37 | 38 |