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 */
16package com.android.car.trust;
17
18import android.Manifest;
19import android.app.Activity;
20import android.bluetooth.BluetoothDevice;
21import android.car.trust.ICarTrustAgentBleCallback;
22import android.car.trust.ICarTrustAgentBleService;
23import android.car.trust.ICarTrustAgentEnrolmentCallback;
24import android.car.trust.ICarTrustAgentTokenResponseCallback;
25import android.content.ComponentName;
26import android.content.Context;
27import android.content.Intent;
28import android.content.ServiceConnection;
29import android.content.SharedPreferences;
30import android.content.pm.PackageManager;
31import android.os.Bundle;
32import android.os.IBinder;
33import android.os.RemoteException;
34import android.os.UserHandle;
35import android.preference.PreferenceManager;
36import android.util.Log;
37import android.widget.TextView;
38
39/**
40 * Setup activity that binds {@link CarTrustAgentBleService} and starts the enrolment process.
41 */
42public class CarEnrolmentActivity extends Activity {
43
44    private static final String TAG = CarEnrolmentActivity.class.getSimpleName();
45
46    private static final String SP_HANDLE_KEY = "sp-test";
47    private static final int FINE_LOCATION_REQUEST_CODE = 42;
48
49    private final ICarTrustAgentTokenResponseCallback mCarTrustAgentTokenResponseCallback =
50            new ICarTrustAgentTokenResponseCallback.Stub() {
51        @Override
52        public void onEscrowTokenAdded(byte[] token, long handle, int uid) {
53            runOnUiThread(() -> {
54                mPrefs.edit().putLong(SP_HANDLE_KEY, handle).apply();
55                Log.d(TAG, "stored new handle");
56            });
57
58            if (mBluetoothDevice == null) {
59                Log.e(TAG, "No active bluetooth found to add escrow token");
60                return;
61            }
62
63            try {
64                // Notify the enrolment client that escrow token has been added
65                mCarTrustAgentBleService.sendEnrolmentHandle(mBluetoothDevice, handle);
66                appendOutputText("Escrow Token Added. Handle: " + handle);
67                appendOutputText("Lock and unlock the device to activate token");
68            } catch (RemoteException e) {
69                Log.e(TAG, "Error sendEnrolmentHandle", e);
70            }
71        }
72
73        @Override
74        public void onEscrowTokenRemoved(long handle, boolean successful) {
75            appendOutputText("Escrow token Removed. Handle: " + handle);
76        }
77
78        @Override
79        public void onEscrowTokenActiveStateChanged(long handle, boolean active) {
80            appendOutputText("Is token active? " + active + " handle: " + handle);
81        }
82    };
83
84    private final ICarTrustAgentBleCallback mBleConnectionCallback =
85            new ICarTrustAgentBleCallback.Stub() {
86        @Override
87        public void onBleServerStartSuccess() {
88            appendOutputText("Server started");
89        }
90
91        @Override
92        public void onBleServerStartFailure(int errorCode) {
93            appendOutputText("Server failed to start, error code: " + errorCode);
94        }
95
96        @Override
97        public void onBleDeviceConnected(BluetoothDevice device) {
98            mBluetoothDevice = device;
99            appendOutputText("Device connected: " + device.getName()
100                    + " address: " + device.getAddress());
101        }
102
103        @Override
104        public void onBleDeviceDisconnected(BluetoothDevice device) {
105            mBluetoothDevice = null;
106            appendOutputText("Device disconnected: " + device.getName()
107                    + " address: " + device.getAddress());
108        }
109    };
110
111    private final ICarTrustAgentEnrolmentCallback mEnrolmentCallback =
112            new ICarTrustAgentEnrolmentCallback.Stub() {
113        @Override
114        public void onEnrolmentDataReceived(byte[] token) {
115            appendOutputText("Enrolment data received ");
116            try {
117                addEscrowToken(token);
118            } catch (RemoteException e) {
119                Log.e(TAG, "Error addEscrowToken", e);
120            }
121        }
122    };
123
124    private final ServiceConnection mServiceConnection = new ServiceConnection() {
125        @Override
126        public void onServiceConnected(ComponentName name, IBinder service) {
127            mCarTrustAgentBleServiceBound = true;
128            mCarTrustAgentBleService = ICarTrustAgentBleService.Stub.asInterface(service);
129            try {
130                mCarTrustAgentBleService.registerBleCallback(mBleConnectionCallback);
131                mCarTrustAgentBleService.registerEnrolmentCallback(mEnrolmentCallback);
132                mCarTrustAgentBleService.setTokenResponseCallback(
133                        mCarTrustAgentTokenResponseCallback);
134                mCarTrustAgentBleService.startEnrolmentAdvertising();
135            } catch (RemoteException e) {
136                Log.e(TAG, "Error startEnrolmentAdvertising", e);
137            }
138        }
139
140        @Override
141        public void onServiceDisconnected(ComponentName name) {
142            if (mCarTrustAgentBleService != null) {
143                try {
144                    mCarTrustAgentBleService.unregisterBleCallback(mBleConnectionCallback);
145                    mCarTrustAgentBleService.unregisterEnrolmentCallback(mEnrolmentCallback);
146                    mCarTrustAgentBleService.setTokenResponseCallback(null);
147                    mCarTrustAgentBleService.stopEnrolmentAdvertising();
148                } catch (RemoteException e) {
149                    Log.e(TAG, "Error unregister callbacks", e);
150                }
151                mCarTrustAgentBleService = null;
152            }
153            mCarTrustAgentBleServiceBound = false;
154        }
155    };
156
157    private TextView mOutputText;
158    private BluetoothDevice mBluetoothDevice;
159    private ICarTrustAgentBleService mCarTrustAgentBleService;
160    private boolean mCarTrustAgentBleServiceBound;
161    private SharedPreferences mPrefs;
162
163    @Override
164    protected void onCreate(Bundle savedInstanceState) {
165        super.onCreate(savedInstanceState);
166
167        setContentView(R.layout.car_enrolment_activity);
168        mOutputText = findViewById(R.id.textfield);
169        mPrefs = PreferenceManager.getDefaultSharedPreferences(this /* context */);
170
171        findViewById(R.id.start_button).setOnClickListener((view) -> {
172            Intent bindIntent = new Intent(this, CarTrustAgentBleService.class);
173            bindService(bindIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
174        });
175
176        findViewById(R.id.revoke_trust_button).setOnClickListener((view) -> {
177            if (mCarTrustAgentBleServiceBound) {
178                try {
179                    mCarTrustAgentBleService.revokeTrust();
180                } catch (RemoteException e) {
181                    Log.e(TAG, "Error revokeTrust", e);
182                }
183            }
184        });
185    }
186
187    @Override
188    protected void onResume() {
189        super.onResume();
190
191        if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) !=
192                PackageManager.PERMISSION_GRANTED) {
193            requestPermissions(
194                    new String[] { android.Manifest.permission.ACCESS_FINE_LOCATION },
195                    FINE_LOCATION_REQUEST_CODE);
196        } else {
197            long tokenHandle = getTokenHandle();
198            if (tokenHandle != -1) {
199                Log.d(TAG, "onResume, checking handle active: " + tokenHandle);
200                if (mCarTrustAgentBleServiceBound) {
201                    try {
202                        // Due to the asynchronous nature of isEscrowTokenActive in
203                        // TrustAgentService, query result will be delivered via
204                        // {@link #mCarTrustAgentTokenResponseCallback}
205                        mCarTrustAgentBleService.isEscrowTokenActive(tokenHandle,
206                                UserHandle.myUserId());
207                    } catch (RemoteException e) {
208                        Log.e(TAG, "Error isEscrowTokenActive", e);
209                    }
210                }
211            } else {
212                appendOutputText("No handles found");
213            }
214        }
215    }
216
217    @Override
218    protected void onDestroy() {
219        if (mCarTrustAgentBleServiceBound) {
220            unbindService(mServiceConnection);
221        }
222        super.onDestroy();
223    }
224
225    private void appendOutputText(final String text) {
226        runOnUiThread(() -> mOutputText.append("\n" + text));
227    }
228
229    private void addEscrowToken(byte[] token) throws RemoteException {
230        if (!mCarTrustAgentBleServiceBound) {
231            Log.e(TAG, "No CarTrustAgentBleService bounded");
232            return;
233        }
234        mCarTrustAgentBleService.addEscrowToken(token, UserHandle.myUserId());
235    }
236
237    private long getTokenHandle() {
238        return mPrefs.getLong(SP_HANDLE_KEY, -1);
239    }
240}
241