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.companiondevicesupport.service;
18 
19 import static com.android.car.connecteddevice.util.SafeLog.logd;
20 import static com.android.car.connecteddevice.util.SafeLog.loge;
21 
22 import android.app.Service;
23 import android.bluetooth.BluetoothAdapter;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.ServiceConnection;
29 import android.os.IBinder;
30 
31 import com.android.car.companiondevicesupport.api.external.ConnectedDeviceManagerBinder;
32 import com.android.car.companiondevicesupport.api.internal.association.AssociationBinder;
33 import com.android.car.companiondevicesupport.api.internal.association.IAssociatedDeviceManager;
34 import com.android.car.companiondevicesupport.feature.LocalFeature;
35 import com.android.car.companiondevicesupport.feature.howitzer.ConnectionHowitzer;
36 import com.android.car.connecteddevice.ConnectedDeviceManager;
37 import com.android.car.connecteddevice.util.EventLog;
38 
39 import java.util.ArrayList;
40 import java.util.List;
41 import java.util.concurrent.Executors;
42 import java.util.concurrent.atomic.AtomicBoolean;
43 
44 /**
45  * Early start service that holds a {@link ConnectedDeviceManager} reference to support companion
46  * device features.
47  */
48 public class CompanionDeviceSupportService extends Service {
49 
50     private static final String TAG = "CompanionDeviceSupportService";
51 
52     /**
53      * When a client calls {@link Context#bindService(Intent, ServiceConnection, int)} to get the
54      * {@link IAssociatedDeviceManager}, this action is required in the param {@link Intent}.
55      */
56     public static final String ACTION_BIND_ASSOCIATION =
57             "com.android.car.companiondevicesupport.BIND_ASSOCIATION";
58 
59     /**
60      * When a client calls {@link Context#bindService(Intent, ServiceConnection, int)} to get the
61      * IConnectedDeviceManager, this action is required in the param {@link Intent}.
62      */
63     public static final String ACTION_BIND_CONNECTED_DEVICE_MANAGER =
64             "com.android.car.companiondevicesupport.BIND_CONNECTED_DEVICE_MANAGER";
65 
66     private final BroadcastReceiver mBleBroadcastReceiver = new BroadcastReceiver() {
67         @Override
68         public void onReceive(Context context, Intent intent) {
69             if (BluetoothAdapter.ACTION_BLE_STATE_CHANGED.equals(intent.getAction())) {
70                 onBluetoothStateChanged(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1));
71             }
72         }
73     };
74 
75     private final List<LocalFeature> mLocalFeatures = new ArrayList<>();
76 
77     private final AtomicBoolean mIsEveryFeatureInitialized = new AtomicBoolean(false);
78 
79     private ConnectedDeviceManager mConnectedDeviceManager;
80 
81     private ConnectedDeviceManagerBinder mConnectedDeviceManagerBinder;
82 
83     private AssociationBinder mAssociationBinder;
84 
85     @Override
onCreate()86     public void onCreate() {
87         super.onCreate();
88         logd(TAG, "Service created.");
89         EventLog.onServiceStarted();
90         mConnectedDeviceManager = new ConnectedDeviceManager(this);
91         mLocalFeatures.add(new ConnectionHowitzer(this, mConnectedDeviceManager));
92         mConnectedDeviceManagerBinder =
93                 new ConnectedDeviceManagerBinder(mConnectedDeviceManager);
94         mAssociationBinder = new AssociationBinder(mConnectedDeviceManager);
95         registerReceiver(mBleBroadcastReceiver,
96                 new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED));
97         if (BluetoothAdapter.getDefaultAdapter().isLeEnabled()) {
98             initializeFeatures();
99         }
100     }
101 
102     @Override
onBind(Intent intent)103     public IBinder onBind(Intent intent) {
104         logd(TAG, "Service bound.");
105         if (intent == null || intent.getAction() == null) {
106             return null;
107         }
108         String action = intent.getAction();
109         switch (action) {
110             case ACTION_BIND_ASSOCIATION:
111                 return mAssociationBinder;
112             case ACTION_BIND_CONNECTED_DEVICE_MANAGER:
113                 return mConnectedDeviceManagerBinder;
114             default:
115                 loge(TAG, "Unexpected action found while binding: " + action);
116                 return null;
117         }
118     }
119 
120     @Override
onDestroy()121     public void onDestroy() {
122         logd(TAG, "Service destroyed.");
123         unregisterReceiver(mBleBroadcastReceiver);
124         cleanup();
125         super.onDestroy();
126     }
127 
onBluetoothStateChanged(int state)128     private void onBluetoothStateChanged(int state) {
129         logd(TAG, "onBluetoothStateChanged: " + BluetoothAdapter.nameForState(state));
130         switch (state) {
131             case BluetoothAdapter.STATE_BLE_ON:
132                 EventLog.onBleOn();
133                 initializeFeatures();
134                 break;
135             case BluetoothAdapter.STATE_OFF:
136                 cleanup();
137                 break;
138             default:
139                 // Ignore.
140         }
141     }
142 
cleanup()143     private void cleanup() {
144         logd(TAG, "Cleaning up features.");
145         if (!mIsEveryFeatureInitialized.get()) {
146             logd(TAG, "Features are already cleaned up. No need to clean up again.");
147             return;
148         }
149         mConnectedDeviceManager.reset();
150         mIsEveryFeatureInitialized.set(false);
151     }
152 
initializeFeatures()153     private void initializeFeatures() {
154         // Room cannot be accessed on main thread.
155         Executors.defaultThreadFactory().newThread(() -> {
156             logd(TAG, "Initializing features.");
157             if (mIsEveryFeatureInitialized.get()) {
158                 logd(TAG, "Features are already initialized. No need to initialize again.");
159                 return;
160             }
161             mConnectedDeviceManager.start();
162             for (LocalFeature feature : mLocalFeatures) {
163                 feature.start();
164             }
165             mIsEveryFeatureInitialized.set(true);
166         }).start();
167     }
168 
169     /** Returns the service's instance of {@link ConnectedDeviceManager}. */
getConnectedDeviceManager()170     protected ConnectedDeviceManager getConnectedDeviceManager() {
171         return mConnectedDeviceManager;
172     }
173 }
174