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