• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.ui.paintbooth.currentactivity;
18 
19 import android.app.ActivityManager;
20 import android.app.Notification;
21 import android.app.NotificationChannel;
22 import android.app.NotificationManager;
23 import android.app.PendingIntent;
24 import android.app.Service;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.pm.PackageManager;
29 import android.graphics.Color;
30 import android.graphics.PixelFormat;
31 import android.os.Handler;
32 import android.os.IBinder;
33 import android.os.Looper;
34 import android.os.RemoteException;
35 import android.view.Gravity;
36 import android.view.MotionEvent;
37 import android.view.View;
38 import android.view.WindowManager;
39 import android.widget.TextView;
40 import android.widget.Toast;
41 
42 import androidx.core.app.NotificationCompat;
43 import androidx.core.content.ContextCompat;
44 
45 import com.android.car.ui.paintbooth.MainActivity;
46 import com.android.car.ui.paintbooth.R;
47 import com.android.car.ui.paintbooth.currentactivity.ActivityTaskManager.TaskStackListener;
48 
49 import java.util.List;
50 
51 /**
52  * To start the service:
53  * adb shell am start-foreground-service -n com.android.car.ui.paintbooth/.CurrentActivityService
54  *
55  * To stop the service:
56  * adb shell am start-foreground-service -n com.android.car.ui.paintbooth/.CurrentActivityService -a
57  * com.android.car.ui.paintbooth.StopService
58  */
59 public class CurrentActivityService extends Service {
60     private static final int FOREGROUND_SERVICE_ID = 111;
61 
62     private WindowManager mWindowManager;
63     private TextView mTextView;
64     private Handler mHandler;
65 
66     @Override
onCreate()67     public void onCreate() {
68         mHandler = new Handler(Looper.getMainLooper());
69 
70         if (ContextCompat.checkSelfPermission(this, "android.permission.REAL_GET_TASKS")
71                 != PackageManager.PERMISSION_GRANTED) {
72             Toast.makeText(this, "android.permission.REAL_GET_TASKS is not granted!",
73                     Toast.LENGTH_LONG).show();
74         }
75 
76         Intent notificationIntent = new Intent(this, CurrentActivityService.class);
77 
78         PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
79                 notificationIntent, PendingIntent.FLAG_IMMUTABLE);
80 
81         NotificationChannel channel = new NotificationChannel("CurrentActivityService",
82                 "Show current activity",
83                 NotificationManager.IMPORTANCE_DEFAULT);
84         NotificationManager notificationManager =
85                 (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
86         notificationManager.createNotificationChannel(channel);
87 
88         Notification notification =
89                 new NotificationCompat.Builder(this, "CurrentActivityService")
90                         .setSmallIcon(R.drawable.ic_launcher)
91                         .setContentTitle("CurrentActivityService")
92                         .setContentText("Show current activity")
93                         .setContentIntent(pendingIntent).build();
94 
95         startForeground(FOREGROUND_SERVICE_ID, notification);
96 
97         try {
98             ActivityTaskManager.getService().registerTaskStackListener(mTaskStackListener);
99         } catch (RemoteException e) {
100             Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
101         }
102 
103         mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
104         final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
105                 WindowManager.LayoutParams.WRAP_CONTENT,
106                 WindowManager.LayoutParams.WRAP_CONTENT,
107                 WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
108                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
109                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
110                 PixelFormat.TRANSLUCENT);
111 
112         mTextView = new TextView(this);
113         layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
114         layoutParams.x = 0;
115         layoutParams.y = 100;
116         mTextView.setLayoutParams(layoutParams);
117         mTextView.setBackgroundColor(Color.argb(50, 0, 255, 0));
118 
119         mTextView.setOnTouchListener(new View.OnTouchListener() {
120 
121             private int mInitialX = 0;
122             private int mInitialY = 0;
123             private float mInitialTouchX;
124             private float mInitialTouchY;
125 
126             @Override
127             public boolean onTouch(View view, MotionEvent event) {
128                 switch (event.getAction() & MotionEvent.ACTION_MASK) {
129                     case MotionEvent.ACTION_DOWN:
130                         mInitialX = layoutParams.x;
131                         mInitialY = layoutParams.y;
132                         mInitialTouchX = event.getRawX();
133                         mInitialTouchY = event.getRawY();
134                         break;
135                     case MotionEvent.ACTION_MOVE:
136                         WindowManager.LayoutParams layoutParams =
137                                 (WindowManager.LayoutParams) view.getLayoutParams();
138                         layoutParams.x = mInitialX + (int) (event.getRawX() - mInitialTouchX);
139                         layoutParams.y = mInitialY + (int) (event.getRawY() - mInitialTouchY);
140                         mWindowManager.updateViewLayout(view, layoutParams);
141                         return true;
142                     default:
143                         break;
144                 }
145 
146                 return false;
147             }
148         });
149 
150         try {
151             mWindowManager.addView(mTextView, layoutParams);
152         } catch (RuntimeException e) {
153             Toast.makeText(this, "Couldn't display overlay", Toast.LENGTH_SHORT)
154                 .show();
155         }
156 
157         showCurrentTask();
158     }
159 
160     @Override
onStartCommand(Intent intent, int flags, int startId)161     public int onStartCommand(Intent intent, int flags, int startId) {
162         if (MainActivity.STOP_SERVICE.equals(intent.getAction())) {
163             stopSelf();
164         }
165 
166         return START_STICKY;
167     }
168 
169     @Override
onBind(Intent intent)170     public IBinder onBind(Intent intent) {
171         return null;
172     }
173 
174     @Override
onDestroy()175     public void onDestroy() {
176         mHandler.removeCallbacksAndMessages(null);
177         mWindowManager.removeView(mTextView);
178         try {
179             ActivityTaskManager.getService().unregisterTaskStackListener(mTaskStackListener);
180         } catch (RemoteException e) {
181             Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
182         }
183     }
184 
185     /**
186      * This requires system permissions or else it will only fetch the current app and the launcher
187      * app
188      */
showCurrentTask()189     private void showCurrentTask() {
190         try {
191             List<ActivityManager.RunningTaskInfo> tasks =
192                     ActivityTaskManager.getService().getTasks(1);
193             if (!tasks.isEmpty()) {
194                 updateComponentName(tasks.get(0).topActivity);
195             }
196         } catch (RemoteException e) {
197             Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
198         }
199     }
200 
updateComponentName(ComponentName componentName)201     private void updateComponentName(ComponentName componentName) {
202         mHandler.post(() -> {
203             if (mTextView != null && componentName != null) {
204                 mTextView.setText(componentName.flattenToShortString().replace('/', '\n'));
205             }
206         });
207     }
208 
209     private final TaskStackListener mTaskStackListener = new TaskStackListener() {
210         @Override
211         public void onTaskCreated(int taskId, ComponentName componentName) {
212             updateComponentName(componentName);
213         }
214 
215         @Override
216         public void onTaskRemoved(int taskId) {
217             showCurrentTask();
218         }
219 
220         @Override
221         public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo) {
222             updateComponentName(taskInfo.topActivity);
223         }
224 
225         @Override
226         public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
227             updateComponentName(taskInfo.topActivity);
228         }
229     };
230 }
231