Overlay.java 443 lines · 19 KB
Raw
1package com.drmod;
2
3import android.content.Context;
4import android.graphics.Color;
5import android.graphics.PixelFormat;
6import android.os.Build;
7import android.os.Handler;
8import android.os.Looper;
9import android.provider.Settings;
10import android.view.ContextThemeWrapper;
11import android.view.Gravity;
12import android.view.MotionEvent;
13import android.view.View;
14import android.view.ViewGroup;
15import android.view.WindowManager;
16import android.widget.Button;
17import android.widget.CompoundButton;
18import android.widget.FrameLayout;
19import android.widget.LinearLayout;
20import android.widget.TextView;
21import android.widget.ToggleButton;
22
23public class Overlay {
24 private static final String TAG = "DRMod-Overlay";
25 private static native void nativeToggle(int feature, boolean enabled);
26
27 // Stato condiviso: [0]=menu aperto, [1]=overlay nascosto (hitbox solo tocco)
28 public static void init(final Context ctx) {
29 DLog.log(TAG, "init() entered, ctx=" + (ctx != null ? ctx.getClass().getName() : "null"));
30 try {
31 if (Build.VERSION.SDK_INT >= 23 && !Settings.canDrawOverlays(ctx)) {
32 DLog.log(TAG, "canDrawOverlays=false, attempting anyway");
33 } else {
34 DLog.log(TAG, "canDrawOverlays=true");
35 }
36 } catch (Throwable t) {
37 DLog.log(TAG, "Permission check failed: " + t.getMessage());
38 }
39
40 final Context themedCtx = new ContextThemeWrapper(ctx, android.R.style.Theme_DeviceDefault);
41 final WindowManager wm = (WindowManager) themedCtx.getSystemService(Context.WINDOW_SERVICE);
42 DLog.log(TAG, "themedCtx created, wm=" + (wm != null ? "ok" : "NULL"));
43
44 // ===== Menu (contenuto, liberamente trascinabile) =====
45 final LinearLayout menu = new LinearLayout(themedCtx);
46 menu.setOrientation(LinearLayout.VERTICAL);
47 menu.setBackgroundColor(0xCC111111);
48 menu.setPadding(dp(themedCtx, 12), dp(themedCtx, 12), dp(themedCtx, 12), dp(themedCtx, 12));
49
50 TextView title = new TextView(themedCtx);
51 title.setText("DungeonRush Mod");
52 title.setTextColor(Color.WHITE);
53 title.setTextSize(14);
54 title.setPadding(0, 0, 0, dp(themedCtx, 2));
55 menu.addView(title);
56
57 TextView sub = new TextView(themedCtx);
58 sub.setText(" by TeamModdersITA");
59 sub.setTextColor(0xFF999999);
60 sub.setTextSize(10);
61 sub.setPadding(0, 0, 0, dp(themedCtx, 8));
62 menu.addView(sub);
63
64 String[] names = {"Immortality", "Forced Crits", "Divine Forge", "Divine Overdrive", "Minigame Loot", "Forge Exp Substats"};
65 for (int i = 0; i < names.length; i++) {
66 final int idx = i;
67 ToggleButton btn = new ToggleButton(themedCtx);
68 btn.setTextOn(names[i] + " [ON]");
69 btn.setTextOff(names[i] + " [OFF]");
70 btn.setChecked(false);
71 btn.setTextSize(11);
72 btn.setBackgroundColor(0xFF222222);
73 btn.setTextColor(Color.WHITE);
74 btn.setPadding(dp(themedCtx, 8), dp(themedCtx, 4), dp(themedCtx, 8), dp(themedCtx, 4));
75 LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
76 LinearLayout.LayoutParams.MATCH_PARENT,
77 LinearLayout.LayoutParams.WRAP_CONTENT);
78 lp.bottomMargin = dp(themedCtx, 4);
79 btn.setLayoutParams(lp);
80 btn.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
81 public void onCheckedChanged(CompoundButton v, boolean checked) {
82 nativeToggle(idx, checked);
83 }
84 });
85 menu.addView(btn);
86 }
87
88 TextView sep = new TextView(themedCtx);
89 sep.setText("\nAlways ON:");
90 sep.setTextColor(0xFF888888);
91 sep.setTextSize(10);
92 menu.addView(sep);
93
94 TextView status = new TextView(themedCtx);
95 status.setText(" Ad Skip\n Store Bypass\n ACTk Disable");
96 status.setTextColor(0xFF666666);
97 status.setTextSize(10);
98 menu.addView(status);
99
100 DLog.log(TAG, "menu built with " + names.length + " toggles");
101
102 // ===== FAB (ancora fissa, separata dal menu) =====
103 final Button fab = new Button(themedCtx);
104 fab.setText("DR");
105 fab.setTextSize(10);
106 fab.setBackgroundColor(0xAAFF3333);
107 fab.setTextColor(Color.WHITE);
108 fab.setPadding(0, 0, 0, 0);
109 fab.setMinWidth(dp(themedCtx, 40));
110 fab.setMinimumWidth(dp(themedCtx, 40));
111 fab.setMinHeight(dp(themedCtx, 40));
112 fab.setMinimumHeight(dp(themedCtx, 40));
113
114 // Wrapper separati, ognuno è una view indipendente agganciata al decor.
115 final FrameLayout menuRoot = new FrameLayout(themedCtx);
116 menuRoot.addView(menu, new FrameLayout.LayoutParams(
117 FrameLayout.LayoutParams.WRAP_CONTENT,
118 FrameLayout.LayoutParams.WRAP_CONTENT));
119
120 final FrameLayout fabRoot = new FrameLayout(themedCtx);
121 fabRoot.addView(fab, new FrameLayout.LayoutParams(
122 dp(themedCtx, 44), dp(themedCtx, 44)));
123 DLog.log(TAG, "menuRoot + fabRoot built (separate)");
124
125 // Stato: [0]=menu aperto, [1]=nascosto (fab alpha 0 ma toccabile)
126 final boolean[] state = {false, false};
127
128 // Apertura/chiusura menu con doppio click sul FAB? No: click singolo.
129 fab.setOnClickListener(new View.OnClickListener() {
130 public void onClick(View v) {
131 state[1] = false; // riprende da hide comunque
132 state[0] = !state[0];
133 if (state[0]) {
134 // Rendi visibile il menu e posizionalo accanto al FAB (dimensioni fisse).
135 menuRoot.setVisibility(View.VISIBLE);
136 placeMenuNearFab(menuRoot, fabRoot, themedCtx);
137 fabRoot.setVisibility(View.GONE); // overlay sparisce quando menu aperto
138 } else {
139 menuRoot.setVisibility(View.GONE);
140 fabRoot.setVisibility(View.VISIBLE);
141 fab.setAlpha(1f);
142 }
143 }
144 });
145
146 // Drag separati: il menu sposta solo menuRoot, il fab sposta solo fabRoot.
147 makeDraggable(menu, menuRoot, state);
148 makeDraggable(fab, fabRoot, state);
149 DLog.log(TAG, "draggable set (menu->menuRoot, fab->fabRoot)");
150
151 // Bottoni hide/close (vivono nel menu e agiscono su menuRoot + fab)
152 buildButtons(menu, menuRoot, fabRoot, fab, state, themedCtx);
153
154 // All'avvio il menu è nascosto; il FAB resta l'ancora visibile per aprirlo.
155 menuRoot.setVisibility(View.GONE);
156
157 DLog.log(TAG, "scheduling addView on main looper...");
158 new Handler(Looper.getMainLooper()).post(new Runnable() {
159 @Override
160 public void run() {
161 DLog.log(TAG, "addView runnable started on thread="
162 + Thread.currentThread().getName());
163 boolean attached = false;
164 try {
165 attached = attachToActivityDecor(menuRoot, fabRoot, themedCtx);
166 DLog.log(TAG, "attachToActivityDecor=" + attached);
167 } catch (Throwable t) {
168 DLog.log(TAG, "attachToActivityDecor exception: " + t);
169 }
170 if (!attached) {
171 try {
172 WindowManager.LayoutParams mParams = makeWmParams(themedCtx);
173 WindowManager.LayoutParams fParams = makeWmParams(themedCtx);
174 fParams.y = dp(themedCtx, 100);
175 wm.addView(menuRoot, mParams);
176 wm.addView(fabRoot, fParams);
177 DLog.log(TAG, "wm.addView fallback succeeded (menu+fab)");
178 } catch (Throwable t) {
179 DLog.log(TAG, "wm.addView fallback failed: " + t);
180 }
181 }
182 }
183 });
184 DLog.log(TAG, "init() complete");
185 }
186
187 // ===== Punto di aggancio al decor =====
188 private static boolean attachToActivityDecor(View menuChild, View fabChild, Context ctx) {
189 try {
190 Class<?> atCls = Class.forName("android.app.ActivityThread");
191 Object at = atCls.getMethod("currentActivityThread").invoke(null);
192 if (at == null) {
193 DLog.log(TAG, "attach: ActivityThread null");
194 return false;
195 }
196 java.lang.reflect.Field mActivities =
197 atCls.getDeclaredField("mActivities");
198 mActivities.setAccessible(true);
199 Object activities = mActivities.get(at);
200 DLog.log(TAG, "attach: mActivities=" + activities);
201 if (activities == null) return false;
202
203 Object record = null;
204 if (activities instanceof java.util.Map) {
205 java.util.Collection<?> vals =
206 ((java.util.Map<?, ?>) activities).values();
207 if (!vals.isEmpty()) {
208 record = vals.iterator().next();
209 }
210 }
211 DLog.log(TAG, "attach: record=" + record);
212 if (record == null) return false;
213
214 java.lang.reflect.Field actField =
215 record.getClass().getDeclaredField("activity");
216 actField.setAccessible(true);
217 Object activity = actField.get(record);
218 DLog.log(TAG, "attach: activity=" + activity);
219 if (!(activity instanceof android.app.Activity)) return false;
220
221 android.view.ViewGroup decor =
222 (android.view.ViewGroup) ((android.app.Activity) activity)
223 .getWindow().getDecorView();
224 DLog.log(TAG, "attach: decor=" + decor);
225
226 // Menu: default in alto a sinistra, indipendente.
227 android.widget.FrameLayout.LayoutParams mLp =
228 new android.widget.FrameLayout.LayoutParams(
229 android.widget.FrameLayout.LayoutParams.WRAP_CONTENT,
230 android.widget.FrameLayout.LayoutParams.WRAP_CONTENT,
231 android.view.Gravity.TOP | android.view.Gravity.START);
232 mLp.topMargin = dp(ctx, 120);
233 mLp.leftMargin = dp(ctx, 60);
234
235 // FAB: ancorato in alto a sinistra (dove l'ho aperto), indipendente.
236 android.widget.FrameLayout.LayoutParams fLp =
237 new android.widget.FrameLayout.LayoutParams(
238 android.widget.FrameLayout.LayoutParams.WRAP_CONTENT,
239 android.widget.FrameLayout.LayoutParams.WRAP_CONTENT,
240 android.view.Gravity.TOP | android.view.Gravity.START);
241 fLp.topMargin = dp(ctx, 100);
242 fLp.leftMargin = dp(ctx, 8);
243
244 decor.addView(menuChild, mLp);
245 decor.addView(fabChild, fLp);
246 DLog.log(TAG, "attach: menu+fab attached to decor");
247 return true;
248 } catch (Throwable t) {
249 DLog.log(TAG, "attachToActivityDecor exception: " + t);
250 return false;
251 }
252 }
253
254 private static WindowManager.LayoutParams makeWmParams(Context ctx) {
255 int overlayType = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
256 if (Build.VERSION.SDK_INT < 26) {
257 overlayType = WindowManager.LayoutParams.TYPE_PHONE;
258 }
259 WindowManager.LayoutParams p = new WindowManager.LayoutParams(
260 WindowManager.LayoutParams.WRAP_CONTENT,
261 WindowManager.LayoutParams.WRAP_CONTENT,
262 overlayType,
263 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
264 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
265 PixelFormat.TRANSLUCENT);
266 p.gravity = Gravity.TOP | Gravity.START;
267 p.x = dp(ctx, 8);
268 p.y = dp(ctx, 100);
269 return p;
270 }
271
272 // ===== Hide / Close =====
273 private static void buildButtons(final LinearLayout menu, final FrameLayout menuRoot,
274 final FrameLayout fabRoot, final Button fab, final boolean[] state, Context ctx) {
275 try {
276 LinearLayout btnRow = new LinearLayout(ctx);
277 btnRow.setOrientation(LinearLayout.HORIZONTAL);
278 LinearLayout.LayoutParams rowLp = new LinearLayout.LayoutParams(
279 LinearLayout.LayoutParams.MATCH_PARENT,
280 LinearLayout.LayoutParams.WRAP_CONTENT);
281 rowLp.topMargin = dp(ctx, 4);
282 btnRow.setLayoutParams(rowLp);
283
284 Button hideBtn = new Button(ctx);
285 hideBtn.setText("hide");
286 hideBtn.setTextSize(11);
287 hideBtn.setBackgroundColor(0xFF222222);
288 hideBtn.setTextColor(Color.WHITE);
289
290 Button closeBtn = new Button(ctx);
291 closeBtn.setText("close");
292 closeBtn.setTextSize(11);
293 closeBtn.setBackgroundColor(0xFF222222);
294 closeBtn.setTextColor(Color.WHITE);
295
296 btnRow.addView(hideBtn, new LinearLayout.LayoutParams(
297 0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
298 btnRow.addView(closeBtn, new LinearLayout.LayoutParams(
299 0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
300 menu.addView(btnRow);
301
302 hideBtn.setOnClickListener(new View.OnClickListener() {
303 public void onClick(View v) {
304 state[0] = false;
305 state[1] = true;
306 menuRoot.setVisibility(View.GONE);
307 fabRoot.setVisibility(View.VISIBLE);
308 fab.setAlpha(0f); // ancora nascosta ma toccabile per riaprire
309 }
310 });
311
312 closeBtn.setOnClickListener(new View.OnClickListener() {
313 public void onClick(View v) {
314 state[0] = false;
315 state[1] = false;
316 menuRoot.setVisibility(View.GONE);
317 fabRoot.setVisibility(View.VISIBLE);
318 fab.setAlpha(1f);
319 }
320 });
321 DLog.log(TAG, "hide/close listeners attached");
322 } catch (Throwable t) {
323 DLog.log(TAG, "buildButtons EXCEPTION: " + t);
324 }
325 }
326
327 // ===== Drag: sposta SOLO il view indicato (moveView), clamp + size fissa =====
328 private static void makeDraggable(final View touchView, final View moveView,
329 final boolean[] state) {
330 touchView.setOnTouchListener(new View.OnTouchListener() {
331 int lastX, lastY;
332 boolean moved;
333 public boolean onTouch(View v, MotionEvent event) {
334 if (state[1]) return false;
335 switch (event.getActionMasked()) {
336 case MotionEvent.ACTION_DOWN:
337 lastX = (int) event.getRawX();
338 lastY = (int) event.getRawY();
339 moved = false;
340 return true;
341 case MotionEvent.ACTION_MOVE:
342 int dx = (int) event.getRawX() - lastX;
343 int dy = (int) event.getRawY() - lastY;
344 if (Math.abs(dx) > 5 || Math.abs(dy) > 5) moved = true;
345 lastX = (int) event.getRawX();
346 lastY = (int) event.getRawY();
347 if (moveView.getParent() != null) {
348 moveClamped(moveView, dx, dy);
349 }
350 return true;
351 case MotionEvent.ACTION_UP:
352 if (!moved) {
353 v.performClick();
354 }
355 return moved;
356 }
357 return false;
358 }
359 });
360 DLog.log(TAG, "draggable attached: touch=" + touchView.getClass().getSimpleName()
361 + " move=" + moveView.getClass().getSimpleName());
362 }
363
364 // Sposta il view con: size fissa (no rimpicciolimento AT_MOST) + clamp a schermo.
365 private static void moveClamped(View view, int dx, int dy) {
366 ViewGroup.LayoutParams lp = view.getLayoutParams();
367 if (!(lp instanceof ViewGroup.MarginLayoutParams)) {
368 view.setTranslationX(view.getTranslationX() + dx);
369 view.setTranslationY(view.getTranslationY() + dy);
370 return;
371 }
372 ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp;
373 int w = view.getWidth();
374 int h = view.getHeight();
375 if (w <= 0) w = view.getMeasuredWidth();
376 if (h <= 0) h = view.getMeasuredHeight();
377 boolean known = (w > 0 && h > 0);
378 if (known) {
379 mlp.width = w;
380 mlp.height = h;
381 }
382 int left = mlp.leftMargin + dx;
383 int top = mlp.topMargin + dy;
384 View parent = (View) view.getParent();
385 if (parent != null) {
386 int parentW = parent.getWidth();
387 int parentH = parent.getHeight();
388 left = Math.max(0, left);
389 top = Math.max(0, top);
390 if (known && parentW > 0) left = Math.min(left, Math.max(0, parentW - w));
391 if (known && parentH > 0) top = Math.min(top, Math.max(0, parentH - h));
392 } else {
393 if (left < 0) left = 0;
394 if (top < 0) top = 0;
395 }
396 mlp.leftMargin = left;
397 mlp.topMargin = top;
398 view.setLayoutParams(mlp);
399 }
400
401 // Posiziona il menu accanto al FAB (a destra se c'è spazio, altrimenti a sinistra),
402 // sempre con clamp a schermo.
403 private static void placeMenuNearFab(View menuRoot, View fabRoot, Context ctx) {
404 ViewGroup.LayoutParams mlp0 = menuRoot.getLayoutParams();
405 ViewGroup.LayoutParams flp0 = fabRoot.getLayoutParams();
406 int fx = 8, fy = 100;
407 if (flp0 instanceof ViewGroup.MarginLayoutParams) {
408 ViewGroup.MarginLayoutParams f = (ViewGroup.MarginLayoutParams) flp0;
409 fx = f.leftMargin;
410 fy = f.topMargin;
411 }
412 int fw = fabRoot.getWidth();
413 if (fw <= 0) fw = dp(ctx, 44);
414
415 int screenW = ctx.getResources().getDisplayMetrics().widthPixels;
416 int screenH = ctx.getResources().getDisplayMetrics().heightPixels;
417
418 // Dimensioni del menu hardcodate (calibrate a schermo 720x1448 dalla misurazione reale).
419 // Non serve misurare né aprire invisibile: usiamo direttamente i valori fissi.
420 int mw = 350;
421 int mh = 771;
422
423 int left = fx + fw;
424 int top = fy;
425 if (left + mw > screenW) left = fx - mw; // a sinistra del fab se non c'è spazio
426 left = Math.max(0, left);
427 top = Math.max(0, top);
428 if (left + mw > screenW) left = Math.max(0, screenW - mw);
429 if (top + mh > screenH) top = Math.max(0, screenH - mh);
430
431 if (mlp0 instanceof ViewGroup.MarginLayoutParams) {
432 ViewGroup.MarginLayoutParams m = (ViewGroup.MarginLayoutParams) mlp0;
433 m.leftMargin = left;
434 m.topMargin = top;
435 menuRoot.setLayoutParams(m);
436 }
437 DLog.log(TAG, "placeMenuNearFab -> left=" + left + " top=" + top + " mw=" + mw + " mh=" + mh);
438 }
439
440 private static int dp(Context ctx, int val) {
441 return (int) (val * ctx.getResources().getDisplayMetrics().density);
442 }
443}