1 /* 2 * Copyright (C) 2022 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.caruiportrait.common.service; 18 19 import android.app.Service; 20 import android.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.os.Bundle; 25 import android.os.Handler; 26 import android.os.IBinder; 27 import android.os.Message; 28 import android.os.Messenger; 29 import android.os.RemoteException; 30 import android.util.Log; 31 32 import java.util.ArrayList; 33 34 /** 35 * This application service uses the {@link Messenger} class for communicating with clients 36 * {@link CarUiPortraitLauncher}. This allows for remote interaction with a service, without 37 * needing to define an AIDL interface. 38 */ 39 public class CarUiPortraitService extends Service { 40 public static final String TAG = "CarUiPortraitService"; 41 42 // action name for the intent when requested from system UI 43 public static final String REQUEST_FROM_SYSTEM_UI = "REQUEST_FROM_SYSTEM_UI"; 44 45 // key name for the intent's extra that tells the root task view's visibility status 46 public static final String INTENT_EXTRA_IS_IMMERSIVE_MODE_REQUESTED = 47 "INTENT_EXTRA_IS_IMMERSIVE_MODE_REQUESTED"; 48 49 // key name for the intent's extra that tells the component that request the view's 50 // visibility change. 51 public static final String INTENT_EXTRA_IMMERSIVE_MODE_REQUESTED_SOURCE = 52 "INTENT_EXTRA_IMMERSIVE_MODE_REQUESTED_SOURCE"; 53 54 public static final String INTENT_EXTRA_IS_IMMERSIVE_MODE_STATE = 55 "INTENT_EXTRA_IS_IMMERSIVE_MODE_STATE"; 56 57 // action name for the intent when requested from CarUiPortraitLauncher 58 public static final String REQUEST_FROM_LAUNCHER = "REQUEST_FROM_LAUNCHER"; 59 60 // key name for the intent's extra that tells the system bars visibility status 61 public static final String INTENT_EXTRA_HIDE_SYSTEM_BAR_FOR_IMMERSIVE_MODE = 62 "INTENT_EXTRA_HIDE_SYSTEM_BAR_FOR_IMMERSIVE_MODE"; 63 64 // key name for the intent's extra that tells the app grid's visibility status 65 public static final String INTENT_EXTRA_APP_GRID_VISIBILITY_CHANGE = 66 "INTENT_EXTRA_APP_GRID_VISIBILITY_CHANGE"; 67 68 // key name for the intent's extra that tells the notification's visibility status 69 public static final String INTENT_EXTRA_NOTIFICATION_VISIBILITY_CHANGE = 70 "INTENT_EXTRA_NOTIFICATION_VISIBILITY_CHANGE"; 71 72 // key name for the intent's extra that tells the Recents' visibility status 73 public static final String INTENT_EXTRA_RECENTS_VISIBILITY_CHANGE = 74 "INTENT_EXTRA_RECENTS_VISIBILITY_CHANGE"; 75 76 // key name for the intent's extra that tells if suw is in progress 77 public static final String INTENT_EXTRA_SUW_IN_PROGRESS = 78 "INTENT_EXTRA_SUW_IN_PROGRESS"; 79 80 // key name for the intent's extra that tells if task views are ready 81 public static final String INTENT_EXTRA_FG_TASK_VIEW_READY = 82 "INTENT_EXTRA_TASK_VIEW_READY"; 83 84 // key name for the intent's extra that tells if launcher is ready 85 public static final String INTENT_EXTRA_LAUNCHER_READY = 86 "INTENT_EXTRA_LAUNCHER_READY"; 87 88 // key name for the intent's extra that tells if application panel should be collapsed. 89 public static final String INTENT_EXTRA_COLLAPSE_APPLICATION_PANEL = 90 "INTENT_EXTRA_COLLAPSE_APPLICATION_PANEL"; 91 92 // Keeps track of all current registered clients. 93 private final ArrayList<Messenger> mClients = new ArrayList<Messenger>(); 94 95 private final Messenger mMessenger = new Messenger(new IncomingHandler()); 96 97 /** 98 * Command to the service to register a client, receiving callbacks 99 * from the service. The Message's replyTo field must be a Messenger of 100 * the client where callbacks should be sent. 101 */ 102 public static final int MSG_REGISTER_CLIENT = 1; 103 104 /** 105 * Command to the service to unregister a client, it stop receiving callbacks 106 * from the service. The Message's replyTo field must be a Messenger of 107 * the client as previously given with MSG_REGISTER_CLIENT. 108 */ 109 public static final int MSG_UNREGISTER_CLIENT = 2; 110 111 /** 112 * Command to service to set a new value for app grid visibility. 113 */ 114 public static final int MSG_APP_GRID_VISIBILITY_CHANGE = 3; 115 116 /** 117 * Command to service to set a new value when immersive mode is requested or exited. 118 */ 119 public static final int MSG_IMMERSIVE_MODE_REQUESTED = 4; 120 121 /** 122 * Command to service to set a new value when SUW mode is entered or exited. 123 */ 124 public static final int MSG_SUW_IN_PROGRESS = 5; 125 126 /** 127 * Command to service to set a new value when launcher request to hide the systembars. 128 */ 129 public static final int MSG_HIDE_SYSTEM_BAR_FOR_IMMERSIVE = 6; 130 131 /** 132 * Command to service to notify when task views are ready. 133 */ 134 public static final int MSG_FG_TASK_VIEW_READY = 7; 135 136 /** 137 * Command to service to notify when immersive mode changes 138 */ 139 public static final int MSG_IMMERSIVE_MODE_CHANGE = 8; 140 141 /** 142 * Command to service to notify when SysUI is ready and started. 143 */ 144 public static final int MSG_SYSUI_STARTED = 9; 145 146 /** 147 * Command to service to set a new value for notifications visibility. 148 */ 149 public static final int MSG_NOTIFICATIONS_VISIBILITY_CHANGE = 10; 150 151 /** 152 * Command to service to set a new value for Recents visibility. 153 */ 154 public static final int MSG_RECENTS_VISIBILITY_CHANGE = 11; 155 156 /** 157 * Command to service to collapse notification panel if open. 158 */ 159 public static final int MSG_COLLAPSE_APPLICATION = 12; 160 161 private boolean mIsSystemInImmersiveMode; 162 private String mImmersiveModeSource; 163 private boolean mIsSuwInProgress; 164 private BroadcastReceiver mSysUiRequestsReceiver; 165 166 /** 167 * Handler of incoming messages from CarUiPortraitLauncher. 168 */ 169 class IncomingHandler extends Handler { 170 @Override handleMessage(Message msg)171 public void handleMessage(Message msg) { 172 Log.d(TAG, "Received message: " + msg.what); 173 switch (msg.what) { 174 case MSG_SYSUI_STARTED: 175 // value is passed as 0 because launcher just needs a event and no need for val 176 notifyClients(MSG_SYSUI_STARTED, 0); 177 break; 178 case MSG_REGISTER_CLIENT: 179 mClients.add(msg.replyTo); 180 break; 181 case MSG_UNREGISTER_CLIENT: 182 mClients.remove(msg.replyTo); 183 break; 184 case MSG_APP_GRID_VISIBILITY_CHANGE: 185 Intent intent = new Intent(REQUEST_FROM_LAUNCHER); 186 intent.putExtra(INTENT_EXTRA_APP_GRID_VISIBILITY_CHANGE, 187 intToBoolean(msg.arg1)); 188 CarUiPortraitService.this.sendBroadcast(intent); 189 break; 190 case MSG_NOTIFICATIONS_VISIBILITY_CHANGE: 191 Intent notificationIntent = new Intent(REQUEST_FROM_LAUNCHER); 192 notificationIntent.putExtra(INTENT_EXTRA_NOTIFICATION_VISIBILITY_CHANGE, 193 intToBoolean(msg.arg1)); 194 CarUiPortraitService.this.sendBroadcast(notificationIntent); 195 break; 196 case MSG_RECENTS_VISIBILITY_CHANGE: 197 Intent recentsIntent = new Intent(REQUEST_FROM_LAUNCHER); 198 recentsIntent.putExtra(INTENT_EXTRA_RECENTS_VISIBILITY_CHANGE, 199 intToBoolean(msg.arg1)); 200 CarUiPortraitService.this.sendBroadcast(recentsIntent); 201 break; 202 case MSG_HIDE_SYSTEM_BAR_FOR_IMMERSIVE: 203 int val = msg.arg1; 204 Intent hideSysBarIntent = new Intent(REQUEST_FROM_LAUNCHER); 205 Log.d(TAG, "hideSysBarIntent: val = " + val); 206 207 hideSysBarIntent.putExtra(INTENT_EXTRA_HIDE_SYSTEM_BAR_FOR_IMMERSIVE_MODE, val); 208 CarUiPortraitService.this.sendBroadcast(hideSysBarIntent); 209 break; 210 case MSG_FG_TASK_VIEW_READY: 211 Intent taskViewReadyIntent = new Intent(REQUEST_FROM_LAUNCHER); 212 taskViewReadyIntent.putExtra(INTENT_EXTRA_FG_TASK_VIEW_READY, 213 intToBoolean(msg.arg1)); 214 taskViewReadyIntent.putExtra(INTENT_EXTRA_LAUNCHER_READY, 215 intToBoolean(msg.arg1)); 216 CarUiPortraitService.this.sendBroadcast(taskViewReadyIntent); 217 break; 218 default: 219 super.handleMessage(msg); 220 } 221 } 222 } 223 224 @Override onCreate()225 public void onCreate() { 226 mSysUiRequestsReceiver = new BroadcastReceiver() { 227 @Override 228 public void onReceive(Context context, Intent intent) { 229 boolean isImmersive = intent.getBooleanExtra( 230 INTENT_EXTRA_IS_IMMERSIVE_MODE_REQUESTED, false); 231 String source = intent.getStringExtra( 232 INTENT_EXTRA_IMMERSIVE_MODE_REQUESTED_SOURCE); 233 Log.d(TAG, "Immersive request: source = " + source + ", request=" + isImmersive); 234 if (intent.hasExtra(INTENT_EXTRA_IS_IMMERSIVE_MODE_REQUESTED) 235 && isImmersive != mIsSystemInImmersiveMode 236 && source != null 237 && !source.equals(mImmersiveModeSource)) { 238 mIsSystemInImmersiveMode = isImmersive; 239 mImmersiveModeSource = source; 240 Bundle bundle = new Bundle(); 241 bundle.putString(INTENT_EXTRA_IMMERSIVE_MODE_REQUESTED_SOURCE, source); 242 notifyClients(MSG_IMMERSIVE_MODE_REQUESTED, boolToInt(isImmersive), bundle); 243 } 244 245 boolean isImmersiveState = intent.getBooleanExtra( 246 INTENT_EXTRA_IS_IMMERSIVE_MODE_STATE, false); 247 if (intent.hasExtra(INTENT_EXTRA_IS_IMMERSIVE_MODE_STATE)) { 248 notifyClients(MSG_IMMERSIVE_MODE_CHANGE, boolToInt(isImmersiveState)); 249 } 250 251 boolean isSuwInProgress = intent.getBooleanExtra( 252 INTENT_EXTRA_SUW_IN_PROGRESS, false); 253 if (intent.hasExtra(INTENT_EXTRA_SUW_IN_PROGRESS) 254 && isSuwInProgress != mIsSuwInProgress) { 255 mIsSuwInProgress = isSuwInProgress; 256 notifyClients(MSG_SUW_IN_PROGRESS, boolToInt(isSuwInProgress)); 257 } 258 259 if (intent.hasExtra(INTENT_EXTRA_COLLAPSE_APPLICATION_PANEL)) { 260 notifyClients(MSG_COLLAPSE_APPLICATION, 1); 261 } 262 } 263 }; 264 IntentFilter filter = new IntentFilter(); 265 filter.addAction(REQUEST_FROM_SYSTEM_UI); 266 registerReceiver(mSysUiRequestsReceiver, filter); 267 Log.d(TAG, "Portrait service is created"); 268 } 269 270 @Override onBind(Intent intent)271 public IBinder onBind(Intent intent) { 272 return mMessenger.getBinder(); 273 } 274 275 @Override onDestroy()276 public void onDestroy() { 277 super.onDestroy(); 278 unregisterReceiver(mSysUiRequestsReceiver); 279 } 280 notifyClients(int key, int value)281 private void notifyClients(int key, int value) { 282 for (int i = mClients.size() - 1; i >= 0; i--) { 283 try { 284 mClients.get(i).send(Message.obtain(null, key, value, 0)); 285 } catch (RemoteException e) { 286 // The client is dead. Remove it from the list. 287 mClients.remove(i); 288 Log.d(TAG, "A client is removed from the list"); 289 } 290 } 291 } 292 notifyClients(int key, int value, Bundle bundle)293 private void notifyClients(int key, int value, Bundle bundle) { 294 for (int i = mClients.size() - 1; i >= 0; i--) { 295 try { 296 Message msg = Message.obtain(null, key, value, 0); 297 msg.setData(bundle); 298 mClients.get(i).send(msg); 299 } catch (RemoteException e) { 300 // The client is dead. Remove it from the list. 301 mClients.remove(i); 302 Log.d(TAG, "A client is removed from the list"); 303 } 304 } 305 } 306 intToBoolean(int val)307 private boolean intToBoolean(int val) { 308 return val == 1; 309 } 310 boolToInt(Boolean b)311 private static int boolToInt(Boolean b) { 312 return b ? 1 : 0; 313 } 314 } 315