blob: 4ef851e766d2ff8ea0713bae922920b3609810a7 [file] [log] [blame]
/*
* Copyright (C) 2019 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.setupwizardlib;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import com.android.car.setupwizardlib.InitialLockSetupConstants.LockTypes;
import com.android.car.setupwizardlib.InitialLockSetupConstants.SetLockCodes;
import com.android.car.setupwizardlib.InitialLockSetupConstants.ValidateLockFlags;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
/**
* Client for communicating with the InitialLockSetupService in Car Settings.
* This allows a device setup wizard to set the initial lock on a device that does not have one.
*/
public class InitialLockSetupClient implements ServiceConnection {
private static final String INITIAL_LOCK_SERVICE_PACKAGE = "com.android.car.settings";
private static final String INITIAL_LOCK_SERVICE_CLASS_NAME =
INITIAL_LOCK_SERVICE_PACKAGE + ".setupservice.InitialLockSetupService";
private static final Intent INITIAL_LOCK_SERVICE_INTENT = new Intent().setComponent(
new ComponentName(INITIAL_LOCK_SERVICE_PACKAGE, INITIAL_LOCK_SERVICE_CLASS_NAME));
private static final String TAG = "InitialLockSetupClient";
// Arbitrary timeout before giving up on connecting to service.
private static final long CONNECTION_TIMEOUT_MS = 5000;
private final Runnable mTimeoutRunnable = this::connectionTimeout;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private InitialLockListener mInitialLockListener;
private Context mContext;
private IInitialLockSetupService mInitialLockSetupService;
private ValidateLockAsyncTask mCurrentValidateLockTask;
private SaveLockAsyncTask mCurrentSaveLockTask;
private boolean mCurrentlyBinding;
// Tracks whether the connection has timed out to prevent duplicate callbacks to listener.
private boolean mTimedOut;
public InitialLockSetupClient(Context context) {
mContext = context.getApplicationContext();
}
/**
* Starts the connection to the service.
*/
public void startConnection(InitialLockListener listener) {
logVerbose("startConnection");
mInitialLockListener = listener;
if (mInitialLockSetupService != null || mCurrentlyBinding) {
logVerbose("Unable to bind to initial setup service, already connected or connecting.");
return;
}
// Reset whether this has timed out.
mTimedOut = false;
try {
mHandler.postDelayed(mTimeoutRunnable, CONNECTION_TIMEOUT_MS);
mCurrentlyBinding = mContext.bindService(INITIAL_LOCK_SERVICE_INTENT, /* connection= */
this,
Context.BIND_AUTO_CREATE);
} catch (Exception e) {
e.printStackTrace();
mInitialLockListener.onConnectionAttemptFinished(false);
}
if (!mCurrentlyBinding) {
logVerbose("Unable to bind to initial setup service");
}
}
/**
* Stops the connection and removes the active listener. Does nothing if not connected.
*/
public void stopConnection() {
logVerbose("stopConnection");
if (mInitialLockSetupService == null) {
logVerbose("Attempting to disconnect from service when not connected");
return;
}
mContext.unbindService(this);
mInitialLockSetupService = null;
mInitialLockListener = null;
mCurrentlyBinding = false;
}
/**
* Returns whether the client is currently connected to the service.
*/
public boolean isServiceConnected() {
return false;
}
/**
* Fetches the set of {@link LockConfig}s that define the lock constraints for the device.
*/
public void getLockConfigs() {
LockConfigsAsyncTask lockConfigsAsyncTask = new LockConfigsAsyncTask(mInitialLockListener,
mInitialLockSetupService);
lockConfigsAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null);
}
/**
* Returns whether the current password fits the Car Settings password
* criteria. Otherwise returns the related error code (length, character
* types, etc).
*/
public void checkValidPassword(byte[] password) {
logVerbose("checkValidPassword");
if (mCurrentValidateLockTask != null
&& mCurrentValidateLockTask.getStatus() != AsyncTask.Status.FINISHED) {
mCurrentValidateLockTask.cancel(true);
}
mCurrentValidateLockTask = new ValidateLockAsyncTask(
mInitialLockListener, mInitialLockSetupService, LockTypes.PASSWORD);
mCurrentValidateLockTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, password);
}
/**
* Returns whether the current PIN fits the Car Settings PIN criteria.
* Otherwise returns the related error code (length, character types, etc).
*/
public void checkValidPin(byte[] pin) {
logVerbose("checkValidPin");
if (mCurrentValidateLockTask != null
&& mCurrentValidateLockTask.getStatus() != AsyncTask.Status.FINISHED) {
mCurrentValidateLockTask.cancel(true);
}
mCurrentValidateLockTask = new ValidateLockAsyncTask(
mInitialLockListener, mInitialLockSetupService, LockTypes.PIN);
mCurrentValidateLockTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pin);
}
/**
* Returns whether the current pattern fits the Car Settings pattern
* criteria. Otherwise returns the related error code.
*/
public void checkValidPattern(byte[] pattern) {
logVerbose("checkValidPattern");
if (mCurrentValidateLockTask != null
&& mCurrentValidateLockTask.getStatus() != AsyncTask.Status.FINISHED) {
mCurrentValidateLockTask.cancel(true);
}
mCurrentValidateLockTask = new ValidateLockAsyncTask(
mInitialLockListener, mInitialLockSetupService, LockTypes.PATTERN);
mCurrentValidateLockTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pattern);
}
/**
* Calls to the service to set the given password as the initial device
* password.
*/
public void saveLockPassword(byte[] password) {
if (mCurrentSaveLockTask != null
&& mCurrentSaveLockTask.getStatus() != AsyncTask.Status.FINISHED) {
// If a save operation is already started, ignore this. Should not try to save
// multiple locks at once.
Log.e(TAG, "Can't save multiple passwords at once");
return;
}
mCurrentSaveLockTask = new SaveLockAsyncTask(mInitialLockListener,
mInitialLockSetupService, LockTypes.PASSWORD);
mCurrentSaveLockTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, password);
}
/**
* Calls to the service to set the given PIN as the initial device PIN.
*/
public void saveLockPin(byte[] pin) {
if (mCurrentSaveLockTask != null
&& mCurrentSaveLockTask.getStatus() != AsyncTask.Status.FINISHED) {
// If a save operation is already started, ignore this. Should not try to save
// multiple locks at once.
Log.e(TAG, "Can't save multiple passwords at once");
return;
}
mCurrentSaveLockTask = new SaveLockAsyncTask(mInitialLockListener,
mInitialLockSetupService, LockTypes.PIN);
mCurrentSaveLockTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pin);
}
/**
* Calls the service to set the given pattern as the initial device lock
* pattern.
*/
public void saveLockPattern(byte[] pattern) {
if (mCurrentSaveLockTask != null
&& mCurrentSaveLockTask.getStatus() != AsyncTask.Status.FINISHED) {
// If a save operation is already started, ignore this. Should not try to save
// multiple locks at once.
Log.e(TAG, "Can't save multiple passwords at once");
return;
}
mCurrentSaveLockTask = new SaveLockAsyncTask(mInitialLockListener,
mInitialLockSetupService, LockTypes.PATTERN);
mCurrentSaveLockTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, pattern);
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
logVerbose("onServiceConnected");
if (mTimedOut) {
return;
}
mHandler.removeCallbacks(mTimeoutRunnable);
mCurrentlyBinding = false;
mInitialLockSetupService = IInitialLockSetupService.Stub.asInterface(service);
if (mInitialLockListener != null) {
mInitialLockListener.onConnectionAttemptFinished(true);
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
logVerbose("onServiceDisconnected");
mCurrentlyBinding = false;
mInitialLockSetupService = null;
}
@Override
public void onNullBinding(ComponentName name) {
logVerbose("onNullBinding");
if (mTimedOut) {
return;
}
mHandler.removeCallbacks(mTimeoutRunnable);
mCurrentlyBinding = false;
if (mInitialLockListener != null) {
mInitialLockListener.onConnectionAttemptFinished(false);
}
}
@Override
public void onBindingDied(ComponentName name) {
logVerbose("onBindingDied");
if (mTimedOut) {
return;
}
mHandler.removeCallbacks(mTimeoutRunnable);
mCurrentlyBinding = false;
if (mInitialLockListener != null) {
mInitialLockListener.onConnectionAttemptFinished(false);
}
}
private void connectionTimeout() {
logVerbose("connectionTimeout");
if (mInitialLockListener == null) {
return;
}
mInitialLockListener.onConnectionAttemptFinished(false);
mTimedOut = true;
}
private static class LockConfigsAsyncTask extends
AsyncTask<Void, Void, Map<Integer, LockConfig>> {
private WeakReference<InitialLockListener> mInitialLockListener;
private WeakReference<IInitialLockSetupService> mInitialLockSetupService;
LockConfigsAsyncTask(InitialLockListener initialLockListener,
IInitialLockSetupService initialLockSetupService) {
mInitialLockListener = new WeakReference<>(initialLockListener);
mInitialLockSetupService = new WeakReference<>(initialLockSetupService);
}
@Override
protected Map<Integer, LockConfig> doInBackground(Void... voids) {
IInitialLockSetupService initialLockSetupService = mInitialLockSetupService.get();
if (initialLockSetupService == null) {
InitialLockSetupClient.logVerbose(
"Lost reference to service in LockConfigsAsyncTask");
return null;
}
LockConfig passwordConfig, pinConfig, patternConfig;
try {
passwordConfig = initialLockSetupService.getLockConfig(LockTypes.PASSWORD);
pinConfig = initialLockSetupService.getLockConfig(LockTypes.PIN);
patternConfig = initialLockSetupService.getLockConfig(LockTypes.PATTERN);
} catch (RemoteException e) {
e.printStackTrace();
return null;
}
Map<Integer, LockConfig> map = new HashMap<>();
map.put(LockTypes.PASSWORD, passwordConfig);
map.put(LockTypes.PIN, pinConfig);
map.put(LockTypes.PATTERN, patternConfig);
return map;
}
@Override
protected void onPostExecute(Map<Integer, LockConfig> map) {
InitialLockListener listener = mInitialLockListener.get();
if (listener == null) {
return;
}
listener.onGetLockConfigs(map);
}
}
private static class ValidateLockAsyncTask extends AsyncTask<byte[], Void, Integer> {
private WeakReference<InitialLockListener> mInitialLockListener;
private WeakReference<IInitialLockSetupService> mInitialLockSetupService;
private int mLockType;
ValidateLockAsyncTask(
InitialLockListener initialLockListener,
IInitialLockSetupService initialLockSetupService,
@LockTypes int lockType) {
mInitialLockListener = new WeakReference<>(initialLockListener);
mInitialLockSetupService = new WeakReference<>(initialLockSetupService);
mLockType = lockType;
}
@Override
protected Integer doInBackground(byte[]... passwords) {
InitialLockSetupClient.logVerbose("ValidateLockAsyncTask doInBackground");
IInitialLockSetupService initialLockSetupService = mInitialLockSetupService.get();
if (initialLockSetupService == null) {
InitialLockSetupClient.logVerbose(
"Lost reference to service in ValidateLockAsyncTask");
return ValidateLockFlags.INVALID_GENERIC;
}
try {
int output = initialLockSetupService.checkValidLock(mLockType, passwords[0]);
return output;
} catch (RemoteException e) {
e.printStackTrace();
return ValidateLockFlags.INVALID_GENERIC;
}
}
@Override
protected void onPostExecute(Integer resultCode) {
if (isCancelled()) {
return;
}
InitialLockListener listener = mInitialLockListener.get();
if (listener == null) {
return;
}
listener.onLockValidated(resultCode);
}
}
private static class SaveLockAsyncTask extends AsyncTask<byte[], Void, Integer> {
private WeakReference<InitialLockListener> mInitialLockListener;
private WeakReference<IInitialLockSetupService> mInitialLockSetupService;
private int mLockType;
SaveLockAsyncTask(
InitialLockListener initialLockListener,
IInitialLockSetupService initialLockSetupService,
@LockTypes int lockType) {
mInitialLockListener = new WeakReference<>(initialLockListener);
mInitialLockSetupService = new WeakReference<>(initialLockSetupService);
mLockType = lockType;
}
@Override
protected Integer doInBackground(byte[]... passwords) {
InitialLockSetupClient.logVerbose("SaveLockAsyncTask doInBackground");
IInitialLockSetupService initialLockSetupService = mInitialLockSetupService.get();
if (initialLockSetupService == null) {
InitialLockSetupClient.logVerbose(
"Lost reference to service in SaveLockAsyncTask");
return SetLockCodes.FAIL_LOCK_GENERIC;
}
try {
int output = initialLockSetupService.setLock(mLockType, passwords[0]);
return output;
} catch (RemoteException e) {
e.printStackTrace();
return SetLockCodes.FAIL_LOCK_GENERIC;
}
}
@Override
protected void onPostExecute(Integer resultCode) {
InitialLockListener listener = mInitialLockListener.get();
if (listener == null) {
return;
}
listener.onSetLockFinished(resultCode);
}
}
private static void logVerbose(String logStatement) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, logStatement);
}
}
}