| /* |
| * Copyright (C) 2016 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.car.trust; |
| |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothGattServer; |
| import android.bluetooth.BluetoothGattServerCallback; |
| import android.bluetooth.BluetoothManager; |
| import android.content.BroadcastReceiver; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.ServiceConnection; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.service.trust.TrustAgentService; |
| import android.support.v4.content.LocalBroadcastManager; |
| import android.util.Log; |
| import com.android.car.trust.comms.SimpleBleServer; |
| |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * A sample trust agent that demonstrates how to use the escrow token unlock APIs. </p> |
| * |
| * This trust agent runs during direct boot and binds to a BLE service that listens for remote |
| * devices to trigger an unlock. <p/> |
| * |
| * The permissions for this agent must be enabled as priv-app permissions for it to start. |
| */ |
| public class CarBleTrustAgent extends TrustAgentService { |
| public static final String ACTION_REVOKE_TRUST = "revoke-trust-action"; |
| public static final String ACTION_ADD_TOKEN = "add-token-action"; |
| public static final String ACTION_IS_TOKEN_ACTIVE = "is-token-active-action"; |
| public static final String ACTION_REMOVE_TOKEN = "remove-token-action"; |
| public static final String ACTION_UNLOCK_DEVICE = "unlock-device-action"; |
| |
| public static final String ACTION_TOKEN_STATUS_RESULT = "token-status-result-action"; |
| public static final String ACTION_ADD_TOKEN_RESULT = "add-token-result-action"; |
| |
| public static final String INTENT_EXTRA_ESCROW_TOKEN = "extra-escrow-token"; |
| public static final String INTENT_EXTRA_TOKEN_HANDLE = "extra-token-handle"; |
| public static final String INTENT_EXTRA_TOKEN_STATUS = "extra-token-status"; |
| |
| |
| private static final String TAG = "CarBleTrustAgent"; |
| |
| private static final long TRUST_DURATION_MS = TimeUnit.MINUTES.toMicros(5); |
| private static final long BLE_RETRY_MS = TimeUnit.SECONDS.toMillis(1); |
| |
| private CarUnlockService mCarUnlockService; |
| private LocalBroadcastManager mLocalBroadcastManager; |
| |
| private boolean mBleServiceBound; |
| |
| // We cannot directly bind to TrustAgentService since the onBind method is final. |
| // As a result, we communicate with the various UI components using a LocalBroadcastManager. |
| private final BroadcastReceiver mTrustEventReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| String action = intent.getAction(); |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "Received broadcast: " + action); |
| } |
| if (ACTION_REVOKE_TRUST.equals(action)) { |
| revokeTrust(); |
| } else if (ACTION_ADD_TOKEN.equals(action)) { |
| byte[] token = intent.getByteArrayExtra(INTENT_EXTRA_ESCROW_TOKEN); |
| addEscrowToken(token, getCurrentUserHandle()); |
| } else if (ACTION_IS_TOKEN_ACTIVE.equals(action)) { |
| long handle = intent.getLongExtra(INTENT_EXTRA_TOKEN_HANDLE, -1); |
| isEscrowTokenActive(handle, getCurrentUserHandle()); |
| } else if (ACTION_REMOVE_TOKEN.equals(action)) { |
| long handle = intent.getLongExtra(INTENT_EXTRA_TOKEN_HANDLE, -1); |
| removeEscrowToken(handle, getCurrentUserHandle()); |
| } |
| } |
| }; |
| |
| @Override |
| public void onTrustTimeout() { |
| super.onTrustTimeout(); |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "onTrustTimeout(): timeout expired"); |
| } |
| } |
| |
| @Override |
| public void onCreate() { |
| super.onCreate(); |
| |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "Bluetooth trust agent starting up"); |
| } |
| IntentFilter filter = new IntentFilter(); |
| filter.addAction(ACTION_REVOKE_TRUST); |
| filter.addAction(ACTION_ADD_TOKEN); |
| filter.addAction(ACTION_IS_TOKEN_ACTIVE); |
| filter.addAction(ACTION_REMOVE_TOKEN); |
| |
| mLocalBroadcastManager = LocalBroadcastManager.getInstance(this /* context */); |
| mLocalBroadcastManager.registerReceiver(mTrustEventReceiver, filter); |
| |
| // If the user is already unlocked, don't bother starting the BLE service. |
| UserManager um = (UserManager) getSystemService(Context.USER_SERVICE); |
| if (!um.isUserUnlocked()) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "User locked, will now bind CarUnlockService"); |
| } |
| Intent intent = new Intent(this, CarUnlockService.class); |
| |
| bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); |
| } else { |
| setManagingTrust(true); |
| } |
| } |
| |
| @Override |
| public void onDestroy() { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "Car Trust agent shutting down"); |
| } |
| mLocalBroadcastManager.unregisterReceiver(mTrustEventReceiver); |
| |
| // Unbind the service to avoid leaks from BLE stack. |
| if (mBleServiceBound) { |
| unbindService(mServiceConnection); |
| } |
| super.onDestroy(); |
| } |
| |
| private SimpleBleServer.ConnectionListener mConnectionListener |
| = new SimpleBleServer.ConnectionListener() { |
| @Override |
| public void onServerStarted() { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "BLE server started"); |
| } |
| } |
| |
| @Override |
| public void onServerStartFailed(int errorCode) { |
| Log.w(TAG, "BLE server failed to start. Error Code: " + errorCode); |
| } |
| |
| @Override |
| public void onDeviceConnected(BluetoothDevice device) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "BLE device connected. Name: " + device.getName() |
| + " Address: " + device.getAddress()); |
| } |
| } |
| }; |
| |
| private CarUnlockService.UnlockServiceCallback mUnlockCallback |
| = new CarUnlockService.UnlockServiceCallback() { |
| @Override |
| public void unlockDevice(byte[] token, long handle) { |
| unlock(token, handle); |
| } |
| }; |
| |
| private ServiceConnection mServiceConnection = new ServiceConnection() { |
| |
| public void onServiceConnected(ComponentName className, IBinder service) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "CarUnlockService connected"); |
| } |
| |
| mBleServiceBound = true; |
| CarUnlockService.UnlockServiceBinder binder |
| = (CarUnlockService.UnlockServiceBinder) service; |
| mCarUnlockService = binder.getService(); |
| mCarUnlockService.addUnlockServiceCallback(mUnlockCallback); |
| mCarUnlockService.addConnectionListener(mConnectionListener); |
| maybeStartBleUnlockService(); |
| } |
| |
| public void onServiceDisconnected(ComponentName arg0) { |
| mCarUnlockService = null; |
| mBleServiceBound = false; |
| } |
| |
| }; |
| |
| private void maybeStartBleUnlockService() { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "Trying to open a Ble GATT server"); |
| } |
| |
| BluetoothManager btManager = |
| (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); |
| BluetoothGattServer mGattServer |
| = btManager.openGattServer(this, new BluetoothGattServerCallback() { |
| @Override |
| public void onConnectionStateChange(BluetoothDevice device, int status, int newState) { |
| super.onConnectionStateChange(device, status, newState); |
| } |
| }); |
| |
| // The BLE stack is started up before the trust agent service, however Gatt capabilities |
| // might not be ready just yet. Keep trying until a GattServer can open up before proceeding |
| // to start the rest of the BLE services. |
| if (mGattServer == null) { |
| Log.e(TAG, "Gatt not available, will try again...in " + BLE_RETRY_MS + "ms"); |
| |
| Handler handler = new Handler(); |
| handler.postDelayed(new Runnable() { |
| @Override |
| public void run() { |
| maybeStartBleUnlockService(); |
| } |
| }, BLE_RETRY_MS); |
| } else { |
| mGattServer.close(); |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "GATT available, starting up UnlockService"); |
| } |
| mCarUnlockService.start(); |
| } |
| } |
| |
| private void unlock(byte[] token, long handle) { |
| UserManager um = (UserManager) getSystemService(Context.USER_SERVICE); |
| |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "About to unlock user. Current handle: " + handle |
| + " Time: " + System.currentTimeMillis()); |
| } |
| unlockUserWithToken(handle, token, getCurrentUserHandle()); |
| |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "Attempted to unlock user, is user unlocked? " + um.isUserUnlocked() |
| + " Time: " + System.currentTimeMillis()); |
| } |
| setManagingTrust(true); |
| |
| if (um.isUserUnlocked()) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, getString(R.string.trust_granted_explanation)); |
| } |
| grantTrust("Granting trust from escrow token", |
| TRUST_DURATION_MS, FLAG_GRANT_TRUST_DISMISS_KEYGUARD); |
| // Trust has been granted, disable the BLE server. This trust agent service does |
| // not need to receive additional BLE data. |
| unbindService(mServiceConnection); |
| } |
| } |
| |
| @Override |
| public void onEscrowTokenRemoved(long handle, boolean successful) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "onEscrowTokenRemoved. Handle: " + handle + " successful? " + successful); |
| } |
| } |
| |
| @Override |
| public void onEscrowTokenStateReceived(long handle, int tokenState) { |
| boolean isActive = tokenState == TOKEN_STATE_ACTIVE; |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "Token handle: " + handle + " isActive: " + isActive); |
| } |
| |
| Intent intent = new Intent(); |
| intent.setAction(ACTION_TOKEN_STATUS_RESULT); |
| intent.putExtra(INTENT_EXTRA_TOKEN_STATUS, isActive); |
| |
| mLocalBroadcastManager.sendBroadcast(intent); |
| } |
| |
| @Override |
| public void onEscrowTokenAdded(byte[] token, long handle, UserHandle user) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "onEscrowTokenAdded, handle: " + handle); |
| } |
| |
| Intent intent = new Intent(); |
| intent.setAction(ACTION_ADD_TOKEN_RESULT); |
| intent.putExtra(INTENT_EXTRA_TOKEN_HANDLE, handle); |
| |
| mLocalBroadcastManager.sendBroadcast(intent); |
| } |
| |
| private UserHandle getCurrentUserHandle() { |
| return UserHandle.of(UserHandle.myUserId()); |
| } |
| } |