content: /**
   * Copyright (C) 2014 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 android.service.trust;
  
  import android.Manifest;
  import android.annotation.IntDef;
  import android.annotation.NonNull;
  import android.annotation.SdkConstant;
  import android.annotation.SystemApi;
  import android.app.Service;
  import android.app.admin.DevicePolicyManager;
  import android.content.ComponentName;
  import android.content.Context;
  import android.content.Intent;
  import android.content.pm.PackageManager;
  import android.content.pm.ServiceInfo;
  import android.os.Bundle;
  import android.os.Handler;
  import android.os.IBinder;
  import android.os.Message;
  import android.os.PersistableBundle;
  import android.os.RemoteException;
  import android.os.UserHandle;
  import android.os.UserManager;
  import android.util.Log;
  import android.util.Slog;
  
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.util.List;
  
  /**
   * A service that notifies the system about whether it believes the environment of the device
   * to be trusted.
   *
   * <p>Trust agents may only be provided by the platform. It is expected that there is only
   * one trust agent installed on the platform. In the event there is more than one,
   * either trust agent can enable trust.
   * </p>
   *
   * <p>To extend this class, you must declare the service in your manifest file with
   * the {@link android.Manifest.permission#BIND_TRUST_AGENT} permission
   * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
   * <pre>
   * &lt;service android:name=".TrustAgent"
   *          android:label="&#64;string/service_name"
   *          android:permission="android.permission.BIND_TRUST_AGENT">
   *     &lt;intent-filter>
   *         &lt;action android:name="android.service.trust.TrustAgentService" />
   *     &lt;/intent-filter>
   *     &lt;meta-data android:name="android.service.trust.trustagent"
   *          android:value="&#64;xml/trust_agent" />
   * &lt;/service></pre>
   *
   * <p>The associated meta-data file can specify an activity that is accessible through Settings
   * and should allow configuring the trust agent, as defined in
   * {@link android.R.styleable#TrustAgent}. For example:</p>
   *
   * <pre>
   * &lt;trust-agent xmlns:android="http://schemas.android.com/apk/res/android"
   *          android:settingsActivity=".TrustAgentSettings" /></pre>
   *
   * @hide
   */
  @SystemApi
  public class TrustAgentService extends Service {
  
      private final String TAG = TrustAgentService.class.getSimpleName() +
              "[" + getClass().getSimpleName() + "]";
      private static final boolean DEBUG = false;
  
      /**
       * The {@link Intent} that must be declared as handled by the service.
       */
      @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
      public static final String SERVICE_INTERFACE
              = "android.service.trust.TrustAgentService";
  
      /**
       * The name of the {@code meta-data} tag pointing to additional configuration of the trust
       * agent.
       */
      public static final String TRUST_AGENT_META_DATA = "android.service.trust.trustagent";
  
  
      /**
       * Flag for {@link #grantTrust(CharSequence, long, int)} indicating that trust is being granted
       * as the direct result of user action - such as solving a security challenge. The hint is used
       * by the system to optimize the experience. Behavior may vary by device and release, so
       * one should only set this parameter if it meets the above criteria rather than relying on
       * the behavior of any particular device or release.
       */
      public static final int FLAG_GRANT_TRUST_INITIATED_BY_USER = 1 << 0;
  
      /**
       * Flag for {@link #grantTrust(CharSequence, long, int)} indicating that the agent would like
       * to dismiss the keyguard. When using this flag, the {@code TrustAgentService} must ensure
       * it is only set in response to a direct user action with the expectation of dismissing the
       * keyguard.
       */
      public static final int FLAG_GRANT_TRUST_DISMISS_KEYGUARD = 1 << 1;
  
      /** @hide */
      @Retention(RetentionPolicy.SOURCE)
      @IntDef(flag = true, prefix = { "FLAG_GRANT_TRUST_" }, value = {
              FLAG_GRANT_TRUST_INITIATED_BY_USER,
              FLAG_GRANT_TRUST_DISMISS_KEYGUARD,
      })
      public @interface GrantTrustFlags {}
  
  
      /**
       * Int enum indicating that escrow token is active.
       * See {@link #onEscrowTokenStateReceived(long, int)}
       *
       */
      public static final int TOKEN_STATE_ACTIVE = 1;
  
      /**
       * Int enum indicating that escow token is inactive.
       * See {@link #onEscrowTokenStateReceived(long, int)}
       *
       */
      public static final int TOKEN_STATE_INACTIVE = 0;
  
      /** @hide */
      @Retention(RetentionPolicy.SOURCE)
      @IntDef(flag = true, prefix = { "TOKEN_STATE_" }, value = {
              TOKEN_STATE_ACTIVE,
              TOKEN_STATE_INACTIVE,
      })
      public @interface TokenState {}
  
      private static final int MSG_UNLOCK_ATTEMPT = 1;
      private static final int MSG_CONFIGURE = 2;
      private static final int MSG_TRUST_TIMEOUT = 3;
      private static final int MSG_DEVICE_LOCKED = 4;
      private static final int MSG_DEVICE_UNLOCKED = 5;
      private static final int MSG_UNLOCK_LOCKOUT = 6;
      private static final int MSG_ESCROW_TOKEN_ADDED = 7;
      private static final int MSG_ESCROW_TOKEN_STATE_RECEIVED = 8;
      private static final int MSG_ESCROW_TOKEN_REMOVED = 9;
  
      private static final String EXTRA_TOKEN = "token";
      private static final String EXTRA_TOKEN_HANDLE = "token_handle";
      private static final String EXTRA_USER_HANDLE = "user_handle";
      private static final String EXTRA_TOKEN_STATE = "token_state";
      private static final String EXTRA_TOKEN_REMOVED_RESULT = "token_removed_result";
      /**
       * Class containing raw data for a given configuration request.
       */
      private static final class ConfigurationData {
          final IBinder token;
          final List<PersistableBundle> options;
          ConfigurationData(List<PersistableBundle> opts, IBinder t) {
              options = opts;
              token = t;
          }
      }
  
      private ITrustAgentServiceCallback mCallback;
  
      private Runnable mPendingGrantTrustTask;
  
      private boolean mManagingTrust;
  
      // Lock used to access mPendingGrantTrustTask and mCallback.
      private final Object mLock = new Object();
  
      private Handler mHandler = new Handler() {
          public void handleMessage(android.os.Message msg) {
              switch (msg.what) {
                  case MSG_UNLOCK_ATTEMPT:
                      onUnlockAttempt(msg.arg1 != 0);
                      break;
                  case MSG_UNLOCK_LOCKOUT:
                      onDeviceUnlockLockout(msg.arg1);
                      break;
                  case MSG_CONFIGURE: {
                      ConfigurationData data = (ConfigurationData) msg.obj;
                      boolean result = onConfigure(data.options);
                      if (data.token != null) {
                          try {
                              synchronized (mLock) {
                                  mCallback.onConfigureCompleted(result, data.token);
                              }
                          } catch (RemoteException e) {
                              onError("calling onSetTrustAgentFeaturesEnabledCompleted()");
                          }
                      }
                      break;
                  }
                  case MSG_TRUST_TIMEOUT:
                      onTrustTimeout();
                      break;
                  case MSG_DEVICE_LOCKED:
                      onDeviceLocked();
                      break;
                  case MSG_DEVICE_UNLOCKED:
                      onDeviceUnlocked();
                      break;
                  case MSG_ESCROW_TOKEN_ADDED: {
                      Bundle data = msg.getData();
                      byte[] token = data.getByteArray(EXTRA_TOKEN);
                      long handle = data.getLong(EXTRA_TOKEN_HANDLE);
                      UserHandle user = (UserHandle) data.getParcelable(EXTRA_USER_HANDLE);
                      onEscrowTokenAdded(token, handle, user);
                      break;
                  }
                  case MSG_ESCROW_TOKEN_STATE_RECEIVED: {
                      Bundle data = msg.getData();
                      long handle = data.getLong(EXTRA_TOKEN_HANDLE);
                      int tokenState = data.getInt(EXTRA_TOKEN_STATE, TOKEN_STATE_INACTIVE);
                      onEscrowTokenStateReceived(handle, tokenState);
                      break;
                  }
                  case MSG_ESCROW_TOKEN_REMOVED: {
                      Bundle data = msg.getData();
                      long handle = data.getLong(EXTRA_TOKEN_HANDLE);
                      boolean success = data.getBoolean(EXTRA_TOKEN_REMOVED_RESULT);
                      onEscrowTokenRemoved(handle, success);
                      break;
                  }
              }
          }
      };
  
      @Override
      public void onCreate() {
          super.onCreate();
          ComponentName component = new ComponentName(this, getClass());
          try {
              ServiceInfo serviceInfo = getPackageManager().getServiceInfo(component, 0 /* flags */);
              if (!Manifest.permission.BIND_TRUST_AGENT.equals(serviceInfo.permission)) {
                  throw new IllegalStateException(component.flattenToShortString()
                          + " is not declared with the permission "
                          + "\"" + Manifest.permission.BIND_TRUST_AGENT + "\"");
              }
          } catch (PackageManager.NameNotFoundException e) {
              Log.e(TAG, "Can't get ServiceInfo for " + component.toShortString());
          }
      }
  
      /**
       * Called after the user attempts to authenticate in keyguard with their device credentials,
       * such as pin, pattern or password.
       *
       * @param successful true if the user successfully completed the challenge.
       */
      public void onUnlockAttempt(boolean successful) {
      }
  
      /**
       * Called when the timeout provided by the agent expires.  Note that this may be called earlier
       * than requested by the agent if the trust timeout is adjusted by the system or
       * {@link DevicePolicyManager}.  The agent is expected to re-evaluate the trust state and only
       * call {@link #grantTrust(CharSequence, long, boolean)} if the trust state should be
       * continued.
       */
      public void onTrustTimeout() {
      }
  
      /**
       * Called when the device enters a state where a PIN, pattern or
       * password must be entered to unlock it.
       */
      public void onDeviceLocked() {
      }
  
      /**
       * Called when the device leaves a state where a PIN, pattern or
       * password must be entered to unlock it.
       */
      public void onDeviceUnlocked() {
      }
  
      /**
       * Called when the device enters a temporary unlock lockout.
       *
       * <p>This occurs when the user has consecutively failed to unlock the device too many times,
       * and must wait until a timeout has passed to perform another attempt. The user may then only
       * use strong authentication mechanisms (PIN, pattern or password) to unlock the device.
       * Calls to {@link #grantTrust(CharSequence, long, int)} will be ignored until the user has
       * unlocked the device and {@link #onDeviceUnlocked()} is called.
       *
       * @param timeoutMs The amount of time, in milliseconds, that needs to elapse before the user
       *    can attempt to unlock the device again.
       */
      public void onDeviceUnlockLockout(long timeoutMs) {
      }
  
      /**
       * Called when an escrow token is added for user userId.
       *
       * @param token the added token
       * @param handle the handle to the corresponding internal synthetic password. A user is unlocked
       * by presenting both handle and escrow token.
       * @param user the user to which the escrow token is added.
       *
       */
      public void onEscrowTokenAdded(byte[] token, long handle, UserHandle user) {
      }
  
      /**
       * Called when an escrow token state is received upon request.
       *
       * @param handle the handle to the internal synthetic password.
       * @param state the state of the requested escrow token, see {@link TokenState}.
       *
       */
      public void onEscrowTokenStateReceived(long handle, @TokenState int tokenState) {
      }
  
      /**
       * Called when an escrow token is removed.
       *
       * @param handle the handle to the removed the synthetic password.
       * @param successful whether the removing operaiton is achieved.
       *
       */
      public void onEscrowTokenRemoved(long handle, boolean successful) {
      }
  
      private void onError(String msg) {
          Slog.v(TAG, "Remote exception while " + msg);
      }
  
      /**
       * Called when device policy admin wants to enable specific options for agent in response to
       * {@link DevicePolicyManager#setKeyguardDisabledFeatures(ComponentName, int)} and
       * {@link DevicePolicyManager#setTrustAgentConfiguration(ComponentName, ComponentName,
       * PersistableBundle)}.
       * <p>Agents that support configuration options should overload this method and return 'true'.
       *
       * @param options The aggregated list of options or an empty list if no restrictions apply.
       * @return true if it supports configuration options.
       */
      public boolean onConfigure(List<PersistableBundle> options) {
          return false;
      }
  
      /**
       * Call to grant trust on the device.
       *
       * @param message describes why the device is trusted, e.g. "Trusted by location".
       * @param durationMs amount of time in milliseconds to keep the device in a trusted state.
       *    Trust for this agent will automatically be revoked when the timeout expires unless
       *    extended by a subsequent call to this function. The timeout is measured from the
       *    invocation of this function as dictated by {@link SystemClock#elapsedRealtime())}.
       *    For security reasons, the value should be no larger than necessary.
       *    The value may be adjusted by the system as necessary to comply with a policy controlled
       *    by the system or {@link DevicePolicyManager} restrictions. See {@link #onTrustTimeout()}
       *    for determining when trust expires.
       * @param initiatedByUser this is a hint to the system that trust is being granted as the
       *    direct result of user action - such as solving a security challenge. The hint is used
       *    by the system to optimize the experience. Behavior may vary by device and release, so
       *    one should only set this parameter if it meets the above criteria rather than relying on
       *    the behavior of any particular device or release. Corresponds to
       *    {@link #FLAG_GRANT_TRUST_INITIATED_BY_USER}.
       * @throws IllegalStateException if the agent is not currently managing trust.
       *
       * @deprecated use {@link #grantTrust(CharSequence, long, int)} instead.
       */
      @Deprecated
      public final void grantTrust(
              final CharSequence message, final long durationMs, final boolean initiatedByUser) {
          grantTrust(message, durationMs, initiatedByUser ? FLAG_GRANT_TRUST_INITIATED_BY_USER : 0);
      }
  
      /**
       * Call to grant trust on the device.
       *
       * @param message describes why the device is trusted, e.g. "Trusted by location".
       * @param durationMs amount of time in milliseconds to keep the device in a trusted state.
       *    Trust for this agent will automatically be revoked when the timeout expires unless
       *    extended by a subsequent call to this function. The timeout is measured from the
       *    invocation of this function as dictated by {@link SystemClock#elapsedRealtime())}.
       *    For security reasons, the value should be no larger than necessary.
       *    The value may be adjusted by the system as necessary to comply with a policy controlled
       *    by the system or {@link DevicePolicyManager} restrictions. See {@link #onTrustTimeout()}
       *    for determining when trust expires.
       * @param flags TBDocumented
       * @throws IllegalStateException if the agent is not currently managing trust.
       */
      public final void grantTrust(
              final CharSequence message, final long durationMs, @GrantTrustFlags final int flags) {
          synchronized (mLock) {
              if (!mManagingTrust) {
                  throw new IllegalStateException("Cannot grant trust if agent is not managing trust."
                          + " Call setManagingTrust(true) first.");
              }
              if (mCallback != null) {
                  try {
                      mCallback.grantTrust(message.toString(), durationMs, flags);
                  } catch (RemoteException e) {
                      onError("calling enableTrust()");
                  }
              } else {
                  // Remember trust has been granted so we can effectively grant it once the service
                  // is bound.
                  mPendingGrantTrustTask = new Runnable() {
                      @Override
                      public void run() {
                          grantTrust(message, durationMs, flags);
                      }
                  };
              }
          }
      }
  
      /**
       * Call to revoke trust on the device.
       */
      public final void revokeTrust() {
          synchronized (mLock) {
              if (mPendingGrantTrustTask != null) {
                  mPendingGrantTrustTask = null;
              }
              if (mCallback != null) {
                  try {
                      mCallback.revokeTrust();
                  } catch (RemoteException e) {
                      onError("calling revokeTrust()");
                  }
              }
          }
      }
  
      /**
       * Call to notify the system if the agent is ready to manage trust.
       *
       * This property is not persistent across recreating the service and defaults to false.
       * Therefore this method is typically called when initializing the agent in {@link #onCreate}.
       *
       * @param managingTrust indicates if the agent would like to manage trust.
       */
      public final void setManagingTrust(boolean managingTrust) {
          synchronized (mLock) {
              if (mManagingTrust != managingTrust) {
                  mManagingTrust = managingTrust;
                  if (mCallback != null) {
                      try {
                          mCallback.setManagingTrust(managingTrust);
                      } catch (RemoteException e) {
                          onError("calling setManagingTrust()");
                      }
                  }
              }
          }
      }
  
      /**
       * Call to add an escrow token to derive a synthetic password. A synthetic password is an
       * alternaive to the user-set password/pin/pattern in order to unlock encrypted disk. An escrow
       * token can be taken and internally derive the synthetic password. The new added token will not
       * be acivated until the user input the correct PIN/Passcode/Password once.
       *
       * Result will be return by callback {@link #onEscrowTokenAdded(long, int)}
       *
       * @param token an escrow token of high entropy.
       * @param user the user which the escrow token will be added to.
       *
       */
      public final void addEscrowToken(byte[] token, UserHandle user) {
          synchronized (mLock) {
              if (mCallback == null) {
                  Slog.w(TAG, "Cannot add escrow token if the agent is not connecting to framework");
                  throw new IllegalStateException("Trust agent is not connected");
              }
              try {
                  mCallback.addEscrowToken(token, user.getIdentifier());
              } catch (RemoteException e) {
                  onError("calling addEscrowToken");
              }
          }
      }
  
      /**
       * Call to check the active state of an escrow token.
       *
       * Result will be return in callback {@link #onEscrowTokenStateReceived(long, boolean)}
       *
       * @param handle the handle of escrow token to the internal synthetic password.
       * @param user the user which the escrow token is added to.
       *
       */
      public final void isEscrowTokenActive(long handle, UserHandle user) {
          synchronized (mLock) {
              if (mCallback == null) {
                  Slog.w(TAG, "Cannot add escrow token if the agent is not connecting to framework");
                  throw new IllegalStateException("Trust agent is not connected");
              }
              try {
                  mCallback.isEscrowTokenActive(handle, user.getIdentifier());
              } catch (RemoteException e) {
                  onError("calling isEscrowTokenActive");
              }
          }
      }
  
      /**
       * Call to remove the escrow token.
       *
       * Result will be return in callback {@link #onEscrowTokenRemoved(long, boolean)}
       *
       * @param handle the handle of escrow tokent to the internal synthetic password.
       * @param user the user id which the escrow token is added to.
       *
       */
      public final void removeEscrowToken(long handle, UserHandle user) {
          synchronized (mLock) {
              if (mCallback == null) {
                  Slog.w(TAG, "Cannot add escrow token if the agent is not connecting to framework");
                  throw new IllegalStateException("Trust agent is not connected");
              }
              try {
                  mCallback.removeEscrowToken(handle, user.getIdentifier());
              } catch (RemoteException e) {
                  onError("callling removeEscrowToken");
              }
          }
      }
  
      /**
       * Call to unlock user's FBE.
       *
       * @param handle the handle of escrow tokent to the internal synthetic password.
       * @param token the escrow token
       * @param user the user about to be unlocked.
       *
       */
      public final void unlockUserWithToken(long handle, byte[] token, UserHandle user) {
          UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
          if (um.isUserUnlocked(user)) {
              Slog.i(TAG, "User already unlocked");
              return;
          }
  
          synchronized (mLock) {
              if (mCallback == null) {
                  Slog.w(TAG, "Cannot add escrow token if the agent is not connecting to framework");
                  throw new IllegalStateException("Trust agent is not connected");
              }
              try {
                  mCallback.unlockUserWithToken(handle, token, user.getIdentifier());
              } catch (RemoteException e) {
                  onError("calling unlockUserWithToken");
              }
          }
      }
  
      /**
       * Request showing a transient error message on the keyguard.
       * The message will be visible on the lock screen or always on display if possible but can be
       * overridden by other keyguard events of higher priority - eg. fingerprint auth error.
       * Other trust agents may override your message if posted simultaneously.
       *
       * @param message Message to show.
       */
      public final void showKeyguardErrorMessage(@NonNull CharSequence message) {
          if (message == null) {
              throw new IllegalArgumentException("message cannot be null");
          }
          synchronized (mLock) {
              if (mCallback == null) {
                  Slog.w(TAG, "Cannot show message because service is not connected to framework.");
                  throw new IllegalStateException("Trust agent is not connected");
              }
              try {
                  mCallback.showKeyguardErrorMessage(message);
              } catch (RemoteException e) {
                  onError("calling showKeyguardErrorMessage");
              }
          }
      }
  
      @Override
      public final IBinder onBind(Intent intent) {
          if (DEBUG) Slog.v(TAG, "onBind() intent = " + intent);
          return new TrustAgentServiceWrapper();
      }
  
      private final class TrustAgentServiceWrapper extends ITrustAgentService.Stub {
          @Override /* Binder API */
          public void onUnlockAttempt(boolean successful) {
              mHandler.obtainMessage(MSG_UNLOCK_ATTEMPT, successful ? 1 : 0, 0).sendToTarget();
          }
  
          @Override
          public void onUnlockLockout(int timeoutMs) {
              mHandler.obtainMessage(MSG_UNLOCK_LOCKOUT, timeoutMs, 0).sendToTarget();
          }
  
          @Override /* Binder API */
          public void onTrustTimeout() {
              mHandler.sendEmptyMessage(MSG_TRUST_TIMEOUT);
          }
  
          @Override /* Binder API */
          public void onConfigure(List<PersistableBundle> args, IBinder token) {
              mHandler.obtainMessage(MSG_CONFIGURE, new ConfigurationData(args, token))
                      .sendToTarget();
          }
  
          @Override
          public void onDeviceLocked() throws RemoteException {
              mHandler.obtainMessage(MSG_DEVICE_LOCKED).sendToTarget();
          }
  
          @Override
          public void onDeviceUnlocked() throws RemoteException {
              mHandler.obtainMessage(MSG_DEVICE_UNLOCKED).sendToTarget();
          }
  
          @Override /* Binder API */
          public void setCallback(ITrustAgentServiceCallback callback) {
              synchronized (mLock) {
                  mCallback = callback;
                  // The managingTrust property is false implicitly on the server-side, so we only
                  // need to set it here if the agent has decided to manage trust.
                  if (mManagingTrust) {
                      try {
                          mCallback.setManagingTrust(mManagingTrust);
                      } catch (RemoteException e ) {
                          onError("calling setManagingTrust()");
                      }
                  }
                  if (mPendingGrantTrustTask != null) {
                      mPendingGrantTrustTask.run();
                      mPendingGrantTrustTask = null;
                  }
              }
          }
  
          @Override
          public void onEscrowTokenAdded(byte[] token, long handle, UserHandle user) {
              Message msg = mHandler.obtainMessage(MSG_ESCROW_TOKEN_ADDED);
              msg.getData().putByteArray(EXTRA_TOKEN, token);
              msg.getData().putLong(EXTRA_TOKEN_HANDLE, handle);
              msg.getData().putParcelable(EXTRA_USER_HANDLE, user);
              msg.sendToTarget();
          }
  
          public void onTokenStateReceived(long handle, int tokenState) {
              Message msg = mHandler.obtainMessage(MSG_ESCROW_TOKEN_STATE_RECEIVED);
              msg.getData().putLong(EXTRA_TOKEN_HANDLE, handle);
              msg.getData().putInt(EXTRA_TOKEN_STATE, tokenState);
              msg.sendToTarget();
          }
  
          public void onEscrowTokenRemoved(long handle, boolean successful) {
              Message msg = mHandler.obtainMessage(MSG_ESCROW_TOKEN_REMOVED);
              msg.getData().putLong(EXTRA_TOKEN_HANDLE, handle);
              msg.getData().putBoolean(EXTRA_TOKEN_REMOVED_RESULT, successful);
              msg.sendToTarget();
          }
      }
  }
  