1+ package com .symphonyrecords .debugger ;
2+
3+
4+ import android .annotation .SuppressLint ;
5+ import android .app .Notification ;
6+ import android .app .Service ;
7+ import android .content .Context ;
8+ import android .content .Intent ;
9+ import android .graphics .PixelFormat ;
10+ import android .os .Build ;
11+ import android .os .IBinder ;
12+ import android .text .SpannableString ;
13+ import android .text .Spanned ;
14+ import android .text .TextUtils ;
15+ import android .text .style .ForegroundColorSpan ;
16+ import android .util .DisplayMetrics ;
17+ import android .view .Gravity ;
18+ import android .view .LayoutInflater ;
19+ import android .view .MotionEvent ;
20+ import android .view .View ;
21+ import android .view .ViewGroup ;
22+ import android .view .WindowManager ;
23+ import android .widget .RelativeLayout ;
24+ import android .widget .ScrollView ;
25+ import android .widget .TextView ;
26+
27+ import androidx .appcompat .widget .AppCompatImageView ;
28+ import androidx .core .content .ContextCompat ;
29+
30+ import java .lang .ref .WeakReference ;
31+
32+ @ SuppressLint ({"RtlHardcoded" , "ClickableViewAccessibility" , "InflateParams" })
33+ public class DebuggerService extends Service {
34+
35+ private boolean isMinimized = true ;
36+ private boolean isShowing = true ;
37+
38+ private WindowManager mWindowManager ;
39+ RelativeLayout root_view ;
40+ private View mView ;
41+ private TextView textView ;
42+ private ScrollView scrollView ;
43+ private AppCompatImageView btnResize ;
44+ private AppCompatImageView btnClose ;
45+ private AppCompatImageView btnReset ;
46+ private CharSequence msg = "" ;
47+ private Spanned spanColor ;
48+
49+
50+ int paramWidth ;
51+ int paramHeight ;
52+ float scale ;
53+
54+ private static WeakReference <DebuggerService > mWeakReferenceContext ;
55+
56+ public static void init (DebuggerService ctx ) {
57+ mWeakReferenceContext = new WeakReference <>(ctx );
58+ }
59+
60+ /**
61+ * get WeakReference instance of {@link DebuggerService} class
62+ */
63+ public static DebuggerService getContext () {
64+ try {
65+ if (null != mWeakReferenceContext && null != mWeakReferenceContext .get ()) {
66+ return mWeakReferenceContext .get ();
67+ }
68+ } catch (Throwable e ) {
69+ e .printStackTrace ();
70+ }
71+ return null ;
72+ }
73+
74+
75+ @ Override
76+ public IBinder onBind (Intent intent ) {
77+ return null ;
78+ }
79+
80+ @ Override
81+ public void onCreate () {
82+ super .onCreate ();
83+
84+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O ) {
85+ startForeground (1 , new Notification ());
86+ }
87+
88+ init (this );
89+
90+ DisplayMetrics metrics = getResources ().getDisplayMetrics ();
91+ paramWidth = (int ) (metrics .widthPixels * 0.7f );
92+ paramHeight = (int ) (metrics .heightPixels * 0.45f );
93+
94+ scale = metrics .density ;
95+
96+ }
97+
98+ @ Override
99+ public int onStartCommand (Intent intent , int flags , int startId ) {
100+ mWindowManager = (WindowManager ) getSystemService (WINDOW_SERVICE );
101+ initUI ();
102+ moveView ();
103+ return super .onStartCommand (intent , flags , startId );
104+ }
105+
106+ @ Override
107+ public void onDestroy () {
108+ try {
109+ if (null != mView && null != mWindowManager ) {
110+ mWindowManager .removeView (mView );
111+ mWindowManager = null ;
112+ }
113+ } catch (Throwable e ) {
114+ e .printStackTrace ();
115+ }
116+
117+
118+ super .onDestroy ();
119+ }
120+
121+ WindowManager .LayoutParams mWindowsParams ;
122+
123+ private void moveView () {
124+
125+
126+ initializeLayoutParams (paramWidth , paramHeight );
127+
128+
129+ mWindowsParams .gravity = Gravity .TOP | Gravity .LEFT ;
130+ mWindowsParams .y = 100 ;
131+ mWindowManager .addView (mView , mWindowsParams );
132+
133+
134+ mView .setOnTouchListener (new View .OnTouchListener () {
135+
136+ private int initialX ;
137+ private int initialY ;
138+ private float initialTouchX ;
139+ private float initialTouchY ;
140+
141+ long startTime = System .currentTimeMillis ();
142+
143+
144+ @ Override
145+ public boolean onTouch (View v , MotionEvent event ) {
146+
147+
148+ if (System .currentTimeMillis () - startTime <= 300 ) {
149+ return false ;
150+ }
151+
152+
153+ // switch (event.getAction()) {
154+ switch (event .getActionMasked ()) {
155+
156+ case MotionEvent .ACTION_DOWN :
157+ initialX = mWindowsParams .x ;
158+ initialY = mWindowsParams .y ;
159+ initialTouchX = event .getRawX ();
160+ initialTouchY = event .getRawY ();
161+ break ;
162+
163+ case MotionEvent .ACTION_UP :
164+ break ;
165+
166+ case MotionEvent .ACTION_MOVE :
167+ mWindowsParams .x = initialX + (int ) (event .getRawX () - initialTouchX );
168+ mWindowsParams .y = initialY + (int ) (event .getRawY () - initialTouchY );
169+ mWindowManager .updateViewLayout (mView , mWindowsParams );
170+ break ;
171+ }
172+ // mView.invalidate();
173+ return false ;
174+ }
175+ });
176+
177+
178+ btnResize .setOnClickListener (view -> {
179+ if (isMinimized ) {
180+ initializeLayoutParams (WindowManager .LayoutParams .MATCH_PARENT , WindowManager .LayoutParams .MATCH_PARENT );
181+ btnResize .setImageResource (R .drawable .ic_maximize );
182+ isMinimized = false ;
183+ } else {
184+ initializeLayoutParams (paramWidth , paramHeight );
185+ btnResize .setImageResource (R .drawable .ic_minimize );
186+ isMinimized = true ;
187+ }
188+ mWindowManager .updateViewLayout (mView , mWindowsParams );
189+ });
190+
191+
192+ mView .findViewById (R .id .btnDrop ).setOnClickListener (view -> {
193+ if (isShowing ) {
194+ // scrollView.setLayoutParams(new RelativeLayout.LayoutParams(dropSize, dropSize));
195+ btnClose .setVisibility (View .GONE );
196+ btnResize .setVisibility (View .GONE );
197+ btnReset .setVisibility (View .GONE );
198+ root_view .getLayoutParams ().width = (int ) (55 * scale + 0.5f );
199+ root_view .getLayoutParams ().height = (int ) (40 * scale + 0.5f );
200+ isShowing = false ;
201+
202+ } else {
203+ btnClose .setVisibility (View .VISIBLE );
204+ btnResize .setVisibility (View .VISIBLE );
205+ btnReset .setVisibility (View .VISIBLE );
206+ root_view .getLayoutParams ().height = ViewGroup .LayoutParams .MATCH_PARENT ;
207+ root_view .getLayoutParams ().width = ViewGroup .LayoutParams .WRAP_CONTENT ;
208+ btnResize .setImageResource (R .drawable .ic_minimize );
209+ isMinimized = true ;
210+ initializeLayoutParams (paramWidth , paramHeight );
211+ isShowing = true ;
212+ }
213+ mWindowManager .updateViewLayout (mView , mWindowsParams );
214+ });
215+
216+
217+ }
218+
219+ private void initializeLayoutParams (int width , int height ) {
220+ // if you got any problems with FLAG_NOT_FOCUSABLE replace it with FLAG_WATCH_OUTSIDE_TOUCH
221+ // FLAG_WATCH_OUTSIDE_TOUCH will cause keyboard will not pop up and back press will not work
222+ // this is why I replace it with FLAG_NOT_FOCUSABLE
223+ mWindowsParams =
224+ Build .VERSION .SDK_INT >= Build .VERSION_CODES .O
225+ ?
226+ new WindowManager .LayoutParams (width , height , WindowManager .LayoutParams .TYPE_APPLICATION_OVERLAY , WindowManager .LayoutParams .FLAG_NOT_TOUCH_MODAL | WindowManager .LayoutParams .FLAG_NOT_FOCUSABLE , PixelFormat .TRANSLUCENT )
227+ :
228+ new WindowManager .LayoutParams (width , height , WindowManager .LayoutParams .TYPE_PHONE , WindowManager .LayoutParams .FLAG_NOT_TOUCH_MODAL | WindowManager .LayoutParams .FLAG_NOT_FOCUSABLE , PixelFormat .TRANSLUCENT );
229+ }
230+
231+
232+ private void initUI () {
233+ LayoutInflater layoutInflater = (LayoutInflater ) getSystemService (Context .LAYOUT_INFLATER_SERVICE );
234+ if (layoutInflater != null ) {
235+ mView = layoutInflater .inflate (R .layout .debugger_layout , null );
236+ root_view = mView .findViewById (R .id .root_debugger );
237+ scrollView = mView .findViewById (R .id .scroll_view );
238+ btnResize = mView .findViewById (R .id .btnResize );
239+ btnClose = mView .findViewById (R .id .btnClose );
240+ btnReset = mView .findViewById (R .id .btnReset );
241+ btnClose .setOnClickListener (view -> stopSelf ());
242+ btnReset .setOnClickListener (view -> {
243+ if (null != textView ) {
244+ textView .setText (null );
245+ }
246+ msg = "" ;
247+ spanColor = null ;
248+ textView = null ;
249+ });
250+ }
251+ }
252+
253+
254+ /**
255+ * Don't Change the order of this method code lines.
256+ */
257+ private void addTextViewToLayout (String text , int textColor ) {
258+ spanColor = setSpanColor (text , ContextCompat .getColor (this , textColor ));
259+ msg = TextUtils .concat (msg , "\n \n " , spanColor );
260+ if (null == textView ) {
261+ textView = mView .findViewById (R .id .message_output );
262+ textView .setGravity (Gravity .LEFT );
263+ }
264+ textView .setText (msg );
265+
266+ if (!scrollView .canScrollVertically (1 )) {
267+ scrollView .post (() -> {
268+ scrollView .smoothScrollTo (0 , scrollView .getHeight ());
269+ scrollView .fullScroll (ScrollView .FOCUS_DOWN );
270+ });
271+ }
272+ mWindowManager .updateViewLayout (mView , mWindowsParams );
273+ }
274+
275+ private Spanned setSpanColor (String s , int color ) {
276+ SpannableString ss = new SpannableString (s );
277+ ss .setSpan (new ForegroundColorSpan (color ), 0 , s .length (), 0 );
278+ return ss ;
279+ }
280+
281+ public static void setDebugText (String msg , int textColor ) {
282+ if (null != getContext ()) getContext ().addTextViewToLayout (msg , textColor );
283+ }
284+
285+
286+ public static void stop () {
287+ if (null != getContext ()) getContext ().stopSelf ();
288+ }
289+
290+
291+ }
0 commit comments