| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.bluetooth.btservice; |
| |
| import android.app.ActivityManager; |
| import android.app.Service; |
| import android.bluetooth.BluetoothAdapter; |
| import android.bluetooth.BluetoothDevice; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.pm.PackageManager; |
| import android.os.IBinder; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.util.Log; |
| |
| import com.android.bluetooth.BluetoothMetricsProto; |
| import com.android.bluetooth.Utils; |
| |
| /** |
| * Base class for a background service that runs a Bluetooth profile |
| */ |
| public abstract class ProfileService extends Service { |
| private static final boolean DBG = false; |
| |
| public static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; |
| public static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; |
| public static final String BLUETOOTH_PRIVILEGED = |
| android.Manifest.permission.BLUETOOTH_PRIVILEGED; |
| |
| public interface IProfileServiceBinder extends IBinder { |
| /** |
| * Called in {@link #onDestroy()} |
| */ |
| void cleanup(); |
| } |
| |
| //Profile services will not be automatically restarted. |
| //They must be explicitly restarted by AdapterService |
| private static final int PROFILE_SERVICE_MODE = Service.START_NOT_STICKY; |
| private BluetoothAdapter mAdapter; |
| private IProfileServiceBinder mBinder; |
| private final String mName; |
| private AdapterService mAdapterService; |
| private BroadcastReceiver mUserSwitchedReceiver; |
| private boolean mProfileStarted = false; |
| |
| public String getName() { |
| return getClass().getSimpleName(); |
| } |
| |
| protected boolean isAvailable() { |
| return mProfileStarted; |
| } |
| |
| /** |
| * Called in {@link #onCreate()} to init binder interface for this profile service |
| * |
| * @return initialized binder interface for this profile service |
| */ |
| protected abstract IProfileServiceBinder initBinder(); |
| |
| /** |
| * Called in {@link #onCreate()} to init basic stuff in this service |
| */ |
| protected void create() {} |
| |
| /** |
| * Called in {@link #onStartCommand(Intent, int, int)} when the service is started by intent |
| * |
| * @return True in successful condition, False otherwise |
| */ |
| protected abstract boolean start(); |
| |
| /** |
| * Called in {@link #onStartCommand(Intent, int, int)} when the service is stopped by intent |
| * |
| * @return True in successful condition, False otherwise |
| */ |
| protected abstract boolean stop(); |
| |
| /** |
| * Called in {@link #onDestroy()} when this object is completely discarded |
| */ |
| protected void cleanup() {} |
| |
| /** |
| * @param userId is equivalent to the result of ActivityManager.getCurrentUser() |
| */ |
| protected void setCurrentUser(int userId) {} |
| |
| /** |
| * @param userId is equivalent to the result of ActivityManager.getCurrentUser() |
| */ |
| protected void setUserUnlocked(int userId) {} |
| |
| protected ProfileService() { |
| mName = getName(); |
| } |
| |
| @Override |
| public void onCreate() { |
| if (DBG) { |
| Log.d(mName, "onCreate"); |
| } |
| super.onCreate(); |
| mAdapter = BluetoothAdapter.getDefaultAdapter(); |
| mBinder = initBinder(); |
| create(); |
| } |
| |
| @Override |
| public int onStartCommand(Intent intent, int flags, int startId) { |
| if (DBG) { |
| Log.d(mName, "onStartCommand()"); |
| } |
| |
| if (checkCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM) |
| != PackageManager.PERMISSION_GRANTED) { |
| Log.e(mName, "Permission denied!"); |
| return PROFILE_SERVICE_MODE; |
| } |
| |
| if (intent == null) { |
| Log.d(mName, "onStartCommand ignoring null intent."); |
| return PROFILE_SERVICE_MODE; |
| } |
| |
| String action = intent.getStringExtra(AdapterService.EXTRA_ACTION); |
| if (AdapterService.ACTION_SERVICE_STATE_CHANGED.equals(action)) { |
| int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); |
| if (state == BluetoothAdapter.STATE_OFF) { |
| doStop(); |
| } else if (state == BluetoothAdapter.STATE_ON) { |
| doStart(); |
| } |
| } |
| return PROFILE_SERVICE_MODE; |
| } |
| |
| @Override |
| public IBinder onBind(Intent intent) { |
| if (DBG) { |
| Log.d(mName, "onBind"); |
| } |
| if (mAdapter != null && mBinder == null) { |
| // initBinder returned null, you can't bind |
| throw new UnsupportedOperationException("Cannot bind to " + mName); |
| } |
| return mBinder; |
| } |
| |
| @Override |
| public boolean onUnbind(Intent intent) { |
| if (DBG) { |
| Log.d(mName, "onUnbind"); |
| } |
| return super.onUnbind(intent); |
| } |
| |
| /** |
| * Support dumping profile-specific information for dumpsys |
| * |
| * @param sb StringBuilder from the profile. |
| */ |
| public void dump(StringBuilder sb) { |
| sb.append("\nProfile: "); |
| sb.append(mName); |
| sb.append("\n"); |
| } |
| |
| /** |
| * Support dumping scan events from GattService |
| * |
| * @param builder metrics proto builder |
| */ |
| public void dumpProto(BluetoothMetricsProto.BluetoothLog.Builder builder) { |
| // Do nothing |
| } |
| |
| /** |
| * Append an indented String for adding dumpsys support to subclasses. |
| * |
| * @param sb StringBuilder from the profile. |
| * @param s String to indent and append. |
| */ |
| public static void println(StringBuilder sb, String s) { |
| sb.append(" "); |
| sb.append(s); |
| sb.append("\n"); |
| } |
| |
| @Override |
| public void onDestroy() { |
| cleanup(); |
| if (mBinder != null) { |
| mBinder.cleanup(); |
| mBinder = null; |
| } |
| mAdapter = null; |
| super.onDestroy(); |
| } |
| |
| private void doStart() { |
| if (mAdapter == null) { |
| Log.w(mName, "Can't start profile service: device does not have BT"); |
| return; |
| } |
| |
| mAdapterService = AdapterService.getAdapterService(); |
| if (mAdapterService == null) { |
| Log.w(mName, "Could not add this profile because AdapterService is null."); |
| return; |
| } |
| mAdapterService.addProfile(this); |
| |
| IntentFilter filter = new IntentFilter(); |
| filter.addAction(Intent.ACTION_USER_SWITCHED); |
| filter.addAction(Intent.ACTION_USER_UNLOCKED); |
| mUserSwitchedReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| final String action = intent.getAction(); |
| final int userId = |
| intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); |
| if (userId == UserHandle.USER_NULL) { |
| Log.e(mName, "userChangeReceiver received an invalid EXTRA_USER_HANDLE"); |
| return; |
| } |
| if (Intent.ACTION_USER_SWITCHED.equals(action)) { |
| Log.d(mName, "User switched to userId " + userId); |
| setCurrentUser(userId); |
| } else if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { |
| Log.d(mName, "Unlocked userId " + userId); |
| setUserUnlocked(userId); |
| } |
| } |
| }; |
| |
| getApplicationContext().registerReceiver(mUserSwitchedReceiver, filter); |
| int currentUserId = ActivityManager.getCurrentUser(); |
| setCurrentUser(currentUserId); |
| UserManager userManager = UserManager.get(getApplicationContext()); |
| if (userManager.isUserUnlocked(currentUserId)) { |
| setUserUnlocked(currentUserId); |
| } |
| mProfileStarted = start(); |
| if (!mProfileStarted) { |
| Log.e(mName, "Error starting profile. start() returned false."); |
| return; |
| } |
| mAdapterService.onProfileServiceStateChanged(this, BluetoothAdapter.STATE_ON); |
| } |
| |
| private void doStop() { |
| if (!mProfileStarted) { |
| Log.w(mName, "doStop() called, but the profile is not running."); |
| } |
| mProfileStarted = false; |
| if (mAdapterService != null) { |
| mAdapterService.onProfileServiceStateChanged(this, BluetoothAdapter.STATE_OFF); |
| } |
| if (!stop()) { |
| Log.e(mName, "Unable to stop profile"); |
| } |
| if (mAdapterService != null) { |
| mAdapterService.removeProfile(this); |
| } |
| if (mUserSwitchedReceiver != null) { |
| getApplicationContext().unregisterReceiver(mUserSwitchedReceiver); |
| mUserSwitchedReceiver = null; |
| } |
| stopSelf(); |
| } |
| |
| protected BluetoothDevice getDevice(byte[] address) { |
| if (mAdapter != null) { |
| return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address)); |
| } |
| return null; |
| } |
| } |