1 /* 2 * Copyright (C) 2017 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 package com.android.google.gce.gceservice; 17 18 import android.app.Notification; 19 import android.app.NotificationChannel; 20 import android.app.NotificationManager; 21 import android.app.Service; 22 import android.bluetooth.BluetoothAdapter; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.content.res.Configuration; 27 import android.graphics.Point; 28 import android.net.ConnectivityManager; 29 import android.util.Log; 30 import android.os.IBinder; 31 import android.view.Display; 32 import android.view.Surface; 33 import android.view.WindowManager; 34 import java.io.FileDescriptor; 35 import java.io.PrintWriter; 36 import java.util.List; 37 38 /** 39 * Service is started by the BootCompletedReceiver at the end of Android Boot process. 40 * Responsible for emitting final BOOT_COMPLETED message and continued configuration changes. 41 */ 42 public class GceService extends Service { 43 private static final String LOG_TAG = "GceService"; 44 /* Intent sent by the BootCompletedReceiver upon receiving ACTION_BOOT_COMPLETED broadcast. */ 45 public static final String INTENT_ACTION_CONFIGURE = "com.android.google.gce.gceservice.CONFIGURE"; 46 public static final String INTENT_ACTION_NETWORK_CHANGED = "com.android.google.gce.gceservice.NETWORK_CHANGED"; 47 public static final String INTENT_ACTION_BLUETOOTH_CHANGED = "com.android.google.gce.gceservice.BLUETOOTH_CHANGED"; 48 private static final String NOTIFICATION_CHANNEL_ID = "cuttlefish-service"; 49 private static final String NOTIFICATION_CHANNEL_NAME = "Cuttlefish Service"; 50 private static final int NOTIFICATION_ID = 1; 51 52 private final JobExecutor mExecutor = new JobExecutor(); 53 private final EventReporter mEventReporter = new EventReporter(); 54 private final GceBroadcastReceiver mBroadcastReceiver = new GceBroadcastReceiver(); 55 56 private BluetoothChecker mBluetoothChecker = null; 57 private ConnectivityChecker mConnChecker; 58 private GceWifiManager mWifiManager = null; 59 private String mMostRecentAction = null; 60 private WindowManager mWindowManager; 61 62 private int mPreviousRotation; 63 private Point mPreviousScreenBounds; 64 private int mPreviousDpi; 65 66 GceService()67 public GceService() {} 68 69 70 @Override onCreate()71 public void onCreate() { 72 try { 73 super.onCreate(); 74 mEventReporter.reportBootStarted(); 75 registerBroadcastReceivers(); 76 77 mWindowManager = getSystemService(WindowManager.class); 78 mConnChecker = new ConnectivityChecker(this, mEventReporter); 79 mWifiManager = new GceWifiManager(this, mEventReporter, mExecutor); 80 mBluetoothChecker = new BluetoothChecker(this); 81 82 mPreviousRotation = getRotation(); 83 mPreviousScreenBounds = getScreenBounds(); 84 mPreviousDpi = getResources().getConfiguration().densityDpi; 85 86 mExecutor.schedule(mWifiManager); 87 mExecutor.schedule(mBluetoothChecker); 88 mExecutor.schedule(mConnChecker); 89 90 mExecutor.schedule(mEventReporter, mBluetoothChecker.getEnabled()); 91 92 NotificationManager notificationManager = 93 (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 94 NotificationChannel channel = 95 new NotificationChannel( 96 NOTIFICATION_CHANNEL_ID, 97 NOTIFICATION_CHANNEL_NAME, 98 NotificationManager.IMPORTANCE_LOW); 99 notificationManager.createNotificationChannel(channel); 100 101 } catch (Exception e) { 102 Log.e(LOG_TAG, "Exception caught", e); 103 } 104 } 105 106 107 @Override onBind(Intent intent)108 public IBinder onBind(Intent intent) { 109 return null; 110 } 111 112 113 /** Register broadcast listeners. 114 * 115 * Certain intents can no longer be used to start a service or activity, but 116 * can still be registered for, such as CONNECTIVITY_ACTION (Android N). 117 */ registerBroadcastReceivers()118 private void registerBroadcastReceivers() { 119 IntentFilter filter = new IntentFilter(); 120 filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); 121 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 122 this.registerReceiver(mBroadcastReceiver, filter); 123 } 124 getScreenBounds()125 private Point getScreenBounds() { 126 Display display = mWindowManager.getDefaultDisplay(); 127 Point screenBounds = new Point(); 128 display.getRealSize(screenBounds); 129 return screenBounds; 130 } 131 getRotation()132 private int getRotation() { 133 int rot = mWindowManager.getDefaultDisplay().getRotation(); 134 switch (rot) { 135 case Surface.ROTATION_0: 136 return 0; 137 case Surface.ROTATION_90: 138 return 90; 139 case Surface.ROTATION_180: 140 return 180; 141 case Surface.ROTATION_270: 142 return 270; 143 } 144 throw new IllegalStateException("Rotation should be one of 0,90,180,270"); 145 } 146 147 @Override onConfigurationChanged(Configuration config)148 public void onConfigurationChanged(Configuration config) { 149 super.onConfigurationChanged(config); 150 151 int rotation = getRotation(); 152 Point screenBounds = getScreenBounds(); 153 int dpi = config.densityDpi; 154 // NOTE: We cannot rely on config.diff(previous config) here because 155 // diff shows CONFIG_SCREEN_SIZE changes when changing between 3-button 156 // and gesture navigation. We only care about the display bounds. 157 if (rotation == mPreviousRotation && 158 screenBounds.equals(mPreviousScreenBounds) && 159 dpi == mPreviousDpi) { 160 return; 161 } 162 163 int width = screenBounds.x; 164 int height = screenBounds.y; 165 mEventReporter.reportScreenChanged(width, height, dpi, rotation); 166 167 mPreviousRotation = rotation; 168 mPreviousScreenBounds = screenBounds; 169 mPreviousDpi = dpi; 170 } 171 172 173 /** StartService entry point. 174 */ 175 @Override onStartCommand(Intent intent, int flags, int startId)176 public int onStartCommand(Intent intent, int flags, int startId) { 177 if (intent != null) { 178 mMostRecentAction = intent.getAction(); 179 } else { 180 Log.w(LOG_TAG, "Previous execution failed. Retrying."); 181 } 182 183 if (mMostRecentAction == null) { 184 Log.e(LOG_TAG, "Missing intent action."); 185 } 186 187 Notification notification = 188 new Notification.Builder(this, NOTIFICATION_CHANNEL_ID) 189 .setAutoCancel(true) 190 .setContentTitle("Cuttlefish service is running.") 191 .setSmallIcon(android.R.drawable.ic_dialog_info) 192 .setTimeoutAfter(10000) 193 .build(); 194 // Start in the Foreground (and do not stop) so that this service 195 // continues running and reporting events without being killed. 196 startForeground(NOTIFICATION_ID, notification); 197 198 if (INTENT_ACTION_CONFIGURE.equals(mMostRecentAction)) { 199 mExecutor.schedule(mConnChecker); 200 } else if (INTENT_ACTION_NETWORK_CHANGED.equals(mMostRecentAction)) { 201 mExecutor.schedule(mConnChecker); 202 } else if (INTENT_ACTION_BLUETOOTH_CHANGED.equals(mMostRecentAction)) { 203 mExecutor.schedule(mBluetoothChecker); 204 } 205 206 /* If anything goes wrong, make sure we receive intent again. */ 207 return Service.START_STICKY; 208 } 209 210 @Override onDestroy()211 public void onDestroy() { 212 unregisterReceiver(mBroadcastReceiver); 213 } 214 215 /** Dump the virtual device state 216 */ 217 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)218 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 219 pw.println("Virtual Device reporter:"); 220 List<String> messageList = mEventReporter.getMessageList(); 221 for (int i = 0; i < messageList.size(); i++) { 222 pw.println(" " + messageList.get(i)); 223 } 224 pw.println(""); 225 pw.println("Current system service state:"); 226 pw.println(" Network connected: " + mConnChecker.getConnected().isDone()); 227 pw.println(" WiFi configured: " + mWifiManager.getWifiReady().isDone()); 228 pw.println(" Bluetooth enabled: " + mBluetoothChecker.getEnabled().isDone()); 229 pw.println(""); 230 } 231 } 232