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