content: /*
   * Copyright (C) 2011 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 an
   * limitations under the License.
   */
  
  package com.android.server.usb;
  
  import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE;
  import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST;
  import static android.hardware.usb.UsbPortStatus.MODE_AUDIO_ACCESSORY;
  import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK;
  import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE;
  
  import static com.android.internal.usb.DumpUtils.writeAccessory;
  import static com.android.internal.util.dump.DumpUtils.writeStringIfNotNull;
  
  import android.app.ActivityManager;
  import android.app.KeyguardManager;
  import android.app.Notification;
  import android.app.NotificationChannel;
  import android.app.NotificationManager;
  import android.app.PendingIntent;
  import android.content.BroadcastReceiver;
  import android.content.ComponentName;
  import android.content.ContentResolver;
  import android.content.Context;
  import android.content.Intent;
  import android.content.IntentFilter;
  import android.content.SharedPreferences;
  import android.content.pm.PackageManager;
  import android.content.res.Resources;
  import android.debug.AdbManagerInternal;
  import android.debug.IAdbTransport;
  import android.hardware.usb.ParcelableUsbPort;
  import android.hardware.usb.UsbAccessory;
  import android.hardware.usb.UsbConfiguration;
  import android.hardware.usb.UsbConstants;
  import android.hardware.usb.UsbDevice;
  import android.hardware.usb.UsbInterface;
  import android.hardware.usb.UsbManager;
  import android.hardware.usb.UsbPort;
  import android.hardware.usb.UsbPortStatus;
  import android.hardware.usb.gadget.V1_0.GadgetFunction;
  import android.hardware.usb.gadget.V1_0.IUsbGadget;
  import android.hardware.usb.gadget.V1_0.IUsbGadgetCallback;
  import android.hardware.usb.gadget.V1_0.Status;
  import android.hidl.manager.V1_0.IServiceManager;
  import android.hidl.manager.V1_0.IServiceNotification;
  import android.os.BatteryManager;
  import android.os.Environment;
  import android.os.FileUtils;
  import android.os.Handler;
  import android.os.HwBinder;
  import android.os.Looper;
  import android.os.Message;
  import android.os.ParcelFileDescriptor;
  import android.os.RemoteException;
  import android.os.SystemClock;
  import android.os.SystemProperties;
  import android.os.UEventObserver;
  import android.os.UserHandle;
  import android.os.UserManager;
  import android.os.storage.StorageManager;
  import android.os.storage.StorageVolume;
  import android.provider.Settings;
  import android.service.usb.UsbDeviceManagerProto;
  import android.service.usb.UsbHandlerProto;
  import android.util.Pair;
  import android.util.Slog;
  
  import com.android.internal.annotations.GuardedBy;
  import com.android.internal.logging.MetricsLogger;
  import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
  import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
  import com.android.internal.notification.SystemNotificationChannels;
  import com.android.internal.os.SomeArgs;
  import com.android.internal.util.dump.DualDumpOutputStream;
  import com.android.server.FgThread;
  import com.android.server.LocalServices;
  import com.android.server.wm.ActivityTaskManagerInternal;
  
  import java.io.File;
  import java.io.FileDescriptor;
  import java.io.FileNotFoundException;
  import java.io.IOException;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.Locale;
  import java.util.Map;
  import java.util.NoSuchElementException;
  import java.util.Scanner;
  import java.util.Set;
  
  /**
   * UsbDeviceManager manages USB state in device mode.
   */
  public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObserver {
  
      private static final String TAG = UsbDeviceManager.class.getSimpleName();
      private static final boolean DEBUG = false;
  
      /**
       * The name of the xml file in which screen unlocked functions are stored.
       */
      private static final String USB_PREFS_XML = "UsbDeviceManagerPrefs.xml";
  
      /**
       * The SharedPreference setting per user that stores the screen unlocked functions between
       * sessions.
       */
      static final String UNLOCKED_CONFIG_PREF = "usb-screen-unlocked-config-%d";
  
      /**
       * ro.bootmode value when phone boots into usual Android.
       */
      private static final String NORMAL_BOOT = "normal";
  
      private static final String USB_STATE_MATCH =
              "DEVPATH=/devices/virtual/android_usb/android0";
      private static final String ACCESSORY_START_MATCH =
              "DEVPATH=/devices/virtual/misc/usb_accessory";
      private static final String FUNCTIONS_PATH =
              "/sys/class/android_usb/android0/functions";
      private static final String STATE_PATH =
              "/sys/class/android_usb/android0/state";
      private static final String RNDIS_ETH_ADDR_PATH =
              "/sys/class/android_usb/android0/f_rndis/ethaddr";
      private static final String AUDIO_SOURCE_PCM_PATH =
              "/sys/class/android_usb/android0/f_audio_source/pcm";
      private static final String MIDI_ALSA_PATH =
              "/sys/class/android_usb/android0/f_midi/alsa";
  
      private static final int MSG_UPDATE_STATE = 0;
      private static final int MSG_ENABLE_ADB = 1;
      private static final int MSG_SET_CURRENT_FUNCTIONS = 2;
      private static final int MSG_SYSTEM_READY = 3;
      private static final int MSG_BOOT_COMPLETED = 4;
      private static final int MSG_USER_SWITCHED = 5;
      private static final int MSG_UPDATE_USER_RESTRICTIONS = 6;
      private static final int MSG_UPDATE_PORT_STATE = 7;
      private static final int MSG_ACCESSORY_MODE_ENTER_TIMEOUT = 8;
      private static final int MSG_UPDATE_CHARGING_STATE = 9;
      private static final int MSG_UPDATE_HOST_STATE = 10;
      private static final int MSG_LOCALE_CHANGED = 11;
      private static final int MSG_SET_SCREEN_UNLOCKED_FUNCTIONS = 12;
      private static final int MSG_UPDATE_SCREEN_LOCK = 13;
      private static final int MSG_SET_CHARGING_FUNCTIONS = 14;
      private static final int MSG_SET_FUNCTIONS_TIMEOUT = 15;
      private static final int MSG_GET_CURRENT_USB_FUNCTIONS = 16;
      private static final int MSG_FUNCTION_SWITCH_TIMEOUT = 17;
      private static final int MSG_GADGET_HAL_REGISTERED = 18;
  
      private static final int AUDIO_MODE_SOURCE = 1;
  
      // Delay for debouncing USB disconnects.
      // We often get rapid connect/disconnect events when enabling USB functions,
      // which need debouncing.
      private static final int UPDATE_DELAY = 1000;
  
      // Timeout for entering USB request mode.
      // Request is cancelled if host does not configure device within 10 seconds.
      private static final int ACCESSORY_REQUEST_TIMEOUT = 10 * 1000;
  
      private static final String BOOT_MODE_PROPERTY = "ro.bootmode";
  
      private static final String ADB_NOTIFICATION_CHANNEL_ID_TV = "usbdevicemanager.adb.tv";
      private UsbHandler mHandler;
  
      private final Object mLock = new Object();
  
      private final Context mContext;
      private final ContentResolver mContentResolver;
      @GuardedBy("mLock")
      private UsbProfileGroupSettingsManager mCurrentSettings;
      private final boolean mHasUsbAccessory;
      @GuardedBy("mLock")
      private String[] mAccessoryStrings;
      private final UEventObserver mUEventObserver;
  
      private static Set<Integer> sBlackListedInterfaces;
      private HashMap<Long, FileDescriptor> mControlFds;
  
      static {
          sBlackListedInterfaces = new HashSet<>();
          sBlackListedInterfaces.add(UsbConstants.USB_CLASS_AUDIO);
          sBlackListedInterfaces.add(UsbConstants.USB_CLASS_COMM);
          sBlackListedInterfaces.add(UsbConstants.USB_CLASS_HID);
          sBlackListedInterfaces.add(UsbConstants.USB_CLASS_PRINTER);
          sBlackListedInterfaces.add(UsbConstants.USB_CLASS_MASS_STORAGE);
          sBlackListedInterfaces.add(UsbConstants.USB_CLASS_HUB);
          sBlackListedInterfaces.add(UsbConstants.USB_CLASS_CDC_DATA);
          sBlackListedInterfaces.add(UsbConstants.USB_CLASS_CSCID);
          sBlackListedInterfaces.add(UsbConstants.USB_CLASS_CONTENT_SEC);
          sBlackListedInterfaces.add(UsbConstants.USB_CLASS_VIDEO);
          sBlackListedInterfaces.add(UsbConstants.USB_CLASS_WIRELESS_CONTROLLER);
      }
  
      /*
       * Listens for uevent messages from the kernel to monitor the USB state
       */
      private final class UsbUEventObserver extends UEventObserver {
          @Override
          public void onUEvent(UEventObserver.UEvent event) {
              if (DEBUG) Slog.v(TAG, "USB UEVENT: " + event.toString());
  
              String state = event.get("USB_STATE");
              String accessory = event.get("ACCESSORY");
              if (state != null) {
                  mHandler.updateState(state);
              } else if ("START".equals(accessory)) {
                  if (DEBUG) Slog.d(TAG, "got accessory start");
                  startAccessoryMode();
              }
          }
      }
  
      @Override
      public void onKeyguardStateChanged(boolean isShowing) {
          int userHandle = ActivityManager.getCurrentUser();
          boolean secure = mContext.getSystemService(KeyguardManager.class)
                  .isDeviceSecure(userHandle);
          if (DEBUG) {
              Slog.v(TAG, "onKeyguardStateChanged: isShowing:" + isShowing + " secure:" + secure
                      + " user:" + userHandle);
          }
          // We are unlocked when the keyguard is down or non-secure.
          mHandler.sendMessage(MSG_UPDATE_SCREEN_LOCK, (isShowing && secure));
      }
  
      @Override
      public void onAwakeStateChanged(boolean isAwake) {
          // ignore
      }
  
      /** Called when a user is unlocked. */
      public void onUnlockUser(int userHandle) {
          onKeyguardStateChanged(false);
      }
  
      public UsbDeviceManager(Context context, UsbAlsaManager alsaManager,
              UsbSettingsManager settingsManager) {
          mContext = context;
          mContentResolver = context.getContentResolver();
          PackageManager pm = mContext.getPackageManager();
          mHasUsbAccessory = pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY);
          initRndisAddress();
  
          boolean halNotPresent = false;
          try {
              IUsbGadget.getService(true);
          } catch (RemoteException e) {
              Slog.e(TAG, "USB GADGET HAL present but exception thrown", e);
          } catch (NoSuchElementException e) {
              halNotPresent = true;
              Slog.i(TAG, "USB GADGET HAL not present in the device", e);
          }
  
          mControlFds = new HashMap<>();
          FileDescriptor mtpFd = nativeOpenControl(UsbManager.USB_FUNCTION_MTP);
          if (mtpFd == null) {
              Slog.e(TAG, "Failed to open control for mtp");
          }
          mControlFds.put(UsbManager.FUNCTION_MTP, mtpFd);
          FileDescriptor ptpFd = nativeOpenControl(UsbManager.USB_FUNCTION_PTP);
          if (ptpFd == null) {
              Slog.e(TAG, "Failed to open control for ptp");
          }
          mControlFds.put(UsbManager.FUNCTION_PTP, ptpFd);
  
          if (halNotPresent) {
              /**
               * Initialze the legacy UsbHandler
               */
              mHandler = new UsbHandlerLegacy(FgThread.get().getLooper(), mContext, this,
                      alsaManager, settingsManager);
          } else {
              /**
               * Initialize HAL based UsbHandler
               */
              mHandler = new UsbHandlerHal(FgThread.get().getLooper(), mContext, this,
                      alsaManager, settingsManager);
          }
  
          if (nativeIsStartRequested()) {
              if (DEBUG) Slog.d(TAG, "accessory attached at boot");
              startAccessoryMode();
          }
  
          BroadcastReceiver portReceiver = new BroadcastReceiver() {
              @Override
              public void onReceive(Context context, Intent intent) {
                  ParcelableUsbPort port = intent.getParcelableExtra(UsbManager.EXTRA_PORT);
                  UsbPortStatus status = intent.getParcelableExtra(UsbManager.EXTRA_PORT_STATUS);
                  mHandler.updateHostState(
                          port.getUsbPort(context.getSystemService(UsbManager.class)), status);
              }
          };
  
          BroadcastReceiver chargingReceiver = new BroadcastReceiver() {
              @Override
              public void onReceive(Context context, Intent intent) {
                  int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
                  boolean usbCharging = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
                  mHandler.sendMessage(MSG_UPDATE_CHARGING_STATE, usbCharging);
              }
          };
  
          BroadcastReceiver hostReceiver = new BroadcastReceiver() {
              @Override
              public void onReceive(Context context, Intent intent) {
                  Iterator devices = ((UsbManager) context.getSystemService(Context.USB_SERVICE))
                          .getDeviceList().entrySet().iterator();
                  if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
                      mHandler.sendMessage(MSG_UPDATE_HOST_STATE, devices, true);
                  } else {
                      mHandler.sendMessage(MSG_UPDATE_HOST_STATE, devices, false);
                  }
              }
          };
  
          BroadcastReceiver languageChangedReceiver = new BroadcastReceiver() {
              @Override
              public void onReceive(Context context, Intent intent) {
                  mHandler.sendEmptyMessage(MSG_LOCALE_CHANGED);
              }
          };
  
          mContext.registerReceiver(portReceiver,
                  new IntentFilter(UsbManager.ACTION_USB_PORT_CHANGED));
          mContext.registerReceiver(chargingReceiver,
                  new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
  
          IntentFilter filter =
                  new IntentFilter(UsbManager.ACTION_USB_DEVICE_ATTACHED);
          filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
          mContext.registerReceiver(hostReceiver, filter);
  
          mContext.registerReceiver(languageChangedReceiver,
                  new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
  
          // Watch for USB configuration changes
          mUEventObserver = new UsbUEventObserver();
          mUEventObserver.startObserving(USB_STATE_MATCH);
          mUEventObserver.startObserving(ACCESSORY_START_MATCH);
      }
  
      UsbProfileGroupSettingsManager getCurrentSettings() {
          synchronized (mLock) {
              return mCurrentSettings;
          }
      }
  
      String[] getAccessoryStrings() {
          synchronized (mLock) {
              return mAccessoryStrings;
          }
      }
  
      public void systemReady() {
          if (DEBUG) Slog.d(TAG, "systemReady");
  
          LocalServices.getService(ActivityTaskManagerInternal.class).registerScreenObserver(this);
  
          mHandler.sendEmptyMessage(MSG_SYSTEM_READY);
      }
  
      public void bootCompleted() {
          if (DEBUG) Slog.d(TAG, "boot completed");
          mHandler.sendEmptyMessage(MSG_BOOT_COMPLETED);
      }
  
      public void setCurrentUser(int newCurrentUserId, UsbProfileGroupSettingsManager settings) {
          synchronized (mLock) {
              mCurrentSettings = settings;
              mHandler.obtainMessage(MSG_USER_SWITCHED, newCurrentUserId, 0).sendToTarget();
          }
      }
  
      public void updateUserRestrictions() {
          mHandler.sendEmptyMessage(MSG_UPDATE_USER_RESTRICTIONS);
      }
  
      private void startAccessoryMode() {
          if (!mHasUsbAccessory) return;
  
          mAccessoryStrings = nativeGetAccessoryStrings();
          boolean enableAudio = (nativeGetAudioMode() == AUDIO_MODE_SOURCE);
          // don't start accessory mode if our mandatory strings have not been set
          boolean enableAccessory = (mAccessoryStrings != null &&
                  mAccessoryStrings[UsbAccessory.MANUFACTURER_STRING] != null &&
                  mAccessoryStrings[UsbAccessory.MODEL_STRING] != null);
  
          long functions = UsbManager.FUNCTION_NONE;
          if (enableAccessory) {
              functions |= UsbManager.FUNCTION_ACCESSORY;
          }
          if (enableAudio) {
              functions |= UsbManager.FUNCTION_AUDIO_SOURCE;
          }
  
          if (functions != UsbManager.FUNCTION_NONE) {
              mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_ACCESSORY_MODE_ENTER_TIMEOUT),
                      ACCESSORY_REQUEST_TIMEOUT);
              setCurrentFunctions(functions);
          }
      }
  
      private static void initRndisAddress() {
          // configure RNDIS ethernet address based on our serial number using the same algorithm
          // we had been previously using in kernel board files
          final int ETH_ALEN = 6;
          int address[] = new int[ETH_ALEN];
          // first byte is 0x02 to signify a locally administered address
          address[0] = 0x02;
  
          String serial = SystemProperties.get("ro.serialno", "1234567890ABCDEF");
          int serialLength = serial.length();
          // XOR the USB serial across the remaining 5 bytes
          for (int i = 0; i < serialLength; i++) {
              address[i % (ETH_ALEN - 1) + 1] ^= (int) serial.charAt(i);
          }
          String addrString = String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X",
                  address[0], address[1], address[2], address[3], address[4], address[5]);
          try {
              FileUtils.stringToFile(RNDIS_ETH_ADDR_PATH, addrString);
          } catch (IOException e) {
              Slog.e(TAG, "failed to write to " + RNDIS_ETH_ADDR_PATH);
          }
      }
  
      abstract static class UsbHandler extends Handler {
  
          // current USB state
          private boolean mConnected;
          private boolean mHostConnected;
          private boolean mSourcePower;
          private boolean mSinkPower;
          private boolean mConfigured;
          private boolean mAudioAccessoryConnected;
          private boolean mAudioAccessorySupported;
  
          private UsbAccessory mCurrentAccessory;
          private int mUsbNotificationId;
          private boolean mAdbNotificationShown;
          private boolean mUsbCharging;
          private boolean mHideUsbNotification;
          private boolean mSupportsAllCombinations;
          private boolean mScreenLocked;
          private boolean mSystemReady;
          private Intent mBroadcastedIntent;
          private boolean mPendingBootBroadcast;
          private boolean mAudioSourceEnabled;
          private boolean mMidiEnabled;
          private int mMidiCard;
          private int mMidiDevice;
  
          private final Context mContext;
          private final UsbAlsaManager mUsbAlsaManager;
          private final UsbSettingsManager mSettingsManager;
          private NotificationManager mNotificationManager;
  
          protected long mScreenUnlockedFunctions;
          protected boolean mBootCompleted;
          protected boolean mCurrentFunctionsApplied;
          protected boolean mUseUsbNotification;
          protected long mCurrentFunctions;
          protected final UsbDeviceManager mUsbDeviceManager;
          protected final ContentResolver mContentResolver;
          protected SharedPreferences mSettings;
          protected int mCurrentUser;
          protected boolean mCurrentUsbFunctionsReceived;
  
          /**
           * The persistent property which stores whether adb is enabled or not.
           * May also contain vendor-specific default functions for testing purposes.
           */
          protected static final String USB_PERSISTENT_CONFIG_PROPERTY = "persist.sys.usb.config";
  
          UsbHandler(Looper looper, Context context, UsbDeviceManager deviceManager,
                  UsbAlsaManager alsaManager, UsbSettingsManager settingsManager) {
              super(looper);
              mContext = context;
              mUsbDeviceManager = deviceManager;
              mUsbAlsaManager = alsaManager;
              mSettingsManager = settingsManager;
              mContentResolver = context.getContentResolver();
  
              mCurrentUser = ActivityManager.getCurrentUser();
              mScreenLocked = true;
  
              mSettings = getPinnedSharedPrefs(mContext);
              if (mSettings == null) {
                  Slog.e(TAG, "Couldn't load shared preferences");
              } else {
                  mScreenUnlockedFunctions = UsbManager.usbFunctionsFromString(
                          mSettings.getString(
                                  String.format(Locale.ENGLISH, UNLOCKED_CONFIG_PREF, mCurrentUser),
                                  ""));
              }
  
              // We do not show the USB notification if the primary volume supports mass storage.
              // The legacy mass storage UI will be used instead.
              final StorageManager storageManager = StorageManager.from(mContext);
              final StorageVolume primary =
                      storageManager != null ? storageManager.getPrimaryVolume() : null;
  
              boolean massStorageSupported = primary != null && primary.allowMassStorage();
              mUseUsbNotification = !massStorageSupported && mContext.getResources().getBoolean(
                      com.android.internal.R.bool.config_usbChargingMessage);
          }
  
          public void sendMessage(int what, boolean arg) {
              removeMessages(what);
              Message m = Message.obtain(this, what);
              m.arg1 = (arg ? 1 : 0);
              sendMessage(m);
          }
  
          public void sendMessage(int what, Object arg) {
              removeMessages(what);
              Message m = Message.obtain(this, what);
              m.obj = arg;
              sendMessage(m);
          }
  
          public void sendMessage(int what, Object arg, boolean arg1) {
              removeMessages(what);
              Message m = Message.obtain(this, what);
              m.obj = arg;
              m.arg1 = (arg1 ? 1 : 0);
              sendMessage(m);
          }
  
          public void sendMessage(int what, boolean arg1, boolean arg2) {
              removeMessages(what);
              Message m = Message.obtain(this, what);
              m.arg1 = (arg1 ? 1 : 0);
              m.arg2 = (arg2 ? 1 : 0);
              sendMessage(m);
          }
  
          public void sendMessageDelayed(int what, boolean arg, long delayMillis) {
              removeMessages(what);
              Message m = Message.obtain(this, what);
              m.arg1 = (arg ? 1 : 0);
              sendMessageDelayed(m, delayMillis);
          }
  
          public void updateState(String state) {
              int connected, configured;
  
              if ("DISCONNECTED".equals(state)) {
                  connected = 0;
                  configured = 0;
              } else if ("CONNECTED".equals(state)) {
                  connected = 1;
                  configured = 0;
              } else if ("CONFIGURED".equals(state)) {
                  connected = 1;
                  configured = 1;
              } else {
                  Slog.e(TAG, "unknown state " + state);
                  return;
              }
              removeMessages(MSG_UPDATE_STATE);
              if (connected == 1) removeMessages(MSG_FUNCTION_SWITCH_TIMEOUT);
              Message msg = Message.obtain(this, MSG_UPDATE_STATE);
              msg.arg1 = connected;
              msg.arg2 = configured;
              // debounce disconnects to avoid problems bringing up USB tethering
              sendMessageDelayed(msg, (connected == 0) ? UPDATE_DELAY : 0);
          }
  
          public void updateHostState(UsbPort port, UsbPortStatus status) {
              if (DEBUG) {
                  Slog.i(TAG, "updateHostState " + port + " status=" + status);
              }
  
              SomeArgs args = SomeArgs.obtain();
              args.arg1 = port;
              args.arg2 = status;
  
              removeMessages(MSG_UPDATE_PORT_STATE);
              Message msg = obtainMessage(MSG_UPDATE_PORT_STATE, args);
              // debounce rapid transitions of connect/disconnect on type-c ports
              sendMessageDelayed(msg, UPDATE_DELAY);
          }
  
          private void setAdbEnabled(boolean enable) {
              if (DEBUG) Slog.d(TAG, "setAdbEnabled: " + enable);
  
              if (enable) {
                  setSystemProperty(USB_PERSISTENT_CONFIG_PROPERTY, UsbManager.USB_FUNCTION_ADB);
              } else {
                  setSystemProperty(USB_PERSISTENT_CONFIG_PROPERTY, "");
              }
  
              setEnabledFunctions(mCurrentFunctions, true);
              updateAdbNotification(false);
          }
  
          protected boolean isUsbTransferAllowed() {
              UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
              return !userManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER);
          }
  
          private void updateCurrentAccessory() {
              // We are entering accessory mode if we have received a request from the host
              // and the request has not timed out yet.
              boolean enteringAccessoryMode = hasMessages(MSG_ACCESSORY_MODE_ENTER_TIMEOUT);
  
              if (mConfigured && enteringAccessoryMode) {
                  // successfully entered accessory mode
                  String[] accessoryStrings = mUsbDeviceManager.getAccessoryStrings();
                  if (accessoryStrings != null) {
                      UsbSerialReader serialReader = new UsbSerialReader(mContext, mSettingsManager,
                              accessoryStrings[UsbAccessory.SERIAL_STRING]);
  
                      mCurrentAccessory = new UsbAccessory(
                              accessoryStrings[UsbAccessory.MANUFACTURER_STRING],
                              accessoryStrings[UsbAccessory.MODEL_STRING],
                              accessoryStrings[UsbAccessory.DESCRIPTION_STRING],
                              accessoryStrings[UsbAccessory.VERSION_STRING],
                              accessoryStrings[UsbAccessory.URI_STRING],
                              serialReader);
  
                      serialReader.setDevice(mCurrentAccessory);
  
                      Slog.d(TAG, "entering USB accessory mode: " + mCurrentAccessory);
                      // defer accessoryAttached if system is not ready
                      if (mBootCompleted) {
                          mUsbDeviceManager.getCurrentSettings().accessoryAttached(mCurrentAccessory);
                      } // else handle in boot completed
                  } else {
                      Slog.e(TAG, "nativeGetAccessoryStrings failed");
                  }
              } else {
                  if (!enteringAccessoryMode) {
                      notifyAccessoryModeExit();
                  } else if (DEBUG) {
                      Slog.v(TAG, "Debouncing accessory mode exit");
                  }
              }
          }
  
          private void notifyAccessoryModeExit() {
              // make sure accessory mode is off
              // and restore default functions
              Slog.d(TAG, "exited USB accessory mode");
              setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
  
              if (mCurrentAccessory != null) {
                  if (mBootCompleted) {
                      mSettingsManager.usbAccessoryRemoved(mCurrentAccessory);
                  }
                  mCurrentAccessory = null;
              }
          }
  
          protected SharedPreferences getPinnedSharedPrefs(Context context) {
              final File prefsFile = new File(
                      Environment.getDataSystemDeDirectory(UserHandle.USER_SYSTEM), USB_PREFS_XML);
              return context.createDeviceProtectedStorageContext()
                      .getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
          }
  
          private boolean isUsbStateChanged(Intent intent) {
              final Set<String> keySet = intent.getExtras().keySet();
              if (mBroadcastedIntent == null) {
                  for (String key : keySet) {
                      if (intent.getBooleanExtra(key, false)) {
                          return true;
                      }
                  }
              } else {
                  if (!keySet.equals(mBroadcastedIntent.getExtras().keySet())) {
                      return true;
                  }
                  for (String key : keySet) {
                      if (intent.getBooleanExtra(key, false) !=
                              mBroadcastedIntent.getBooleanExtra(key, false)) {
                          return true;
                      }
                  }
              }
              return false;
          }
  
          protected void updateUsbStateBroadcastIfNeeded(long functions) {
              // send a sticky broadcast containing current USB state
              Intent intent = new Intent(UsbManager.ACTION_USB_STATE);
              intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
                      | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
                      | Intent.FLAG_RECEIVER_FOREGROUND);
              intent.putExtra(UsbManager.USB_CONNECTED, mConnected);
              intent.putExtra(UsbManager.USB_HOST_CONNECTED, mHostConnected);
              intent.putExtra(UsbManager.USB_CONFIGURED, mConfigured);
              intent.putExtra(UsbManager.USB_DATA_UNLOCKED,
                      isUsbTransferAllowed() && isUsbDataTransferActive(mCurrentFunctions));
  
              long remainingFunctions = functions;
              while (remainingFunctions != 0) {
                  intent.putExtra(UsbManager.usbFunctionsToString(
                          Long.highestOneBit(remainingFunctions)), true);
                  remainingFunctions -= Long.highestOneBit(remainingFunctions);
              }
  
              // send broadcast intent only if the USB state has changed
              if (!isUsbStateChanged(intent)) {
                  if (DEBUG) {
                      Slog.d(TAG, "skip broadcasting " + intent + " extras: " + intent.getExtras());
                  }
                  return;
              }
  
              if (DEBUG) Slog.d(TAG, "broadcasting " + intent + " extras: " + intent.getExtras());
              sendStickyBroadcast(intent);
              mBroadcastedIntent = intent;
          }
  
          protected void sendStickyBroadcast(Intent intent) {
              mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
          }
  
          private void updateUsbFunctions() {
              updateMidiFunction();
          }
  
          private void updateMidiFunction() {
              boolean enabled = (mCurrentFunctions & UsbManager.FUNCTION_MIDI) != 0;
              if (enabled != mMidiEnabled) {
                  if (enabled) {
                      Scanner scanner = null;
                      try {
                          scanner = new Scanner(new File(MIDI_ALSA_PATH));
                          mMidiCard = scanner.nextInt();
                          mMidiDevice = scanner.nextInt();
                      } catch (FileNotFoundException e) {
                          Slog.e(TAG, "could not open MIDI file", e);
                          enabled = false;
                      } finally {
                          if (scanner != null) {
                              scanner.close();
                          }
                      }
                  }
                  mMidiEnabled = enabled;
              }
              mUsbAlsaManager.setPeripheralMidiState(
                      mMidiEnabled && mConfigured, mMidiCard, mMidiDevice);
          }
  
          private void setScreenUnlockedFunctions() {
              setEnabledFunctions(mScreenUnlockedFunctions, false);
          }
  
          private static class AdbTransport extends IAdbTransport.Stub {
              private final UsbHandler mHandler;
  
              AdbTransport(UsbHandler handler) {
                  mHandler = handler;
              }
  
              @Override
              public void onAdbEnabled(boolean enabled) {
                  mHandler.sendMessage(MSG_ENABLE_ADB, enabled);
              }
          }
  
          /**
           * Returns the functions that are passed down to the low level driver once adb and
           * charging are accounted for.
           */
          long getAppliedFunctions(long functions) {
              if (functions == UsbManager.FUNCTION_NONE) {
                  return getChargingFunctions();
              }
              if (isAdbEnabled()) {
                  return functions | UsbManager.FUNCTION_ADB;
              }
              return functions;
          }
  
          @Override
          public void handleMessage(Message msg) {
              switch (msg.what) {
                  case MSG_UPDATE_STATE:
                      mConnected = (msg.arg1 == 1);
                      mConfigured = (msg.arg2 == 1);
  
                      updateUsbNotification(false);
                      updateAdbNotification(false);
                      if (mBootCompleted) {
                          updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions));
                      }
                      if ((mCurrentFunctions & UsbManager.FUNCTION_ACCESSORY) != 0) {
                          updateCurrentAccessory();
                      }
                      if (mBootCompleted) {
                          if (!mConnected && !hasMessages(MSG_ACCESSORY_MODE_ENTER_TIMEOUT)
                                  && !hasMessages(MSG_FUNCTION_SWITCH_TIMEOUT)) {
                              // restore defaults when USB is disconnected
                              if (!mScreenLocked
                                      && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) {
                                  setScreenUnlockedFunctions();
                              } else {
                                  setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
                              }
                          }
                          updateUsbFunctions();
                      } else {
                          mPendingBootBroadcast = true;
                      }
                      break;
                  case MSG_UPDATE_PORT_STATE:
                      SomeArgs args = (SomeArgs) msg.obj;
                      boolean prevHostConnected = mHostConnected;
                      UsbPort port = (UsbPort) args.arg1;
                      UsbPortStatus status = (UsbPortStatus) args.arg2;
                      mHostConnected = status.getCurrentDataRole() == DATA_ROLE_HOST;
                      mSourcePower = status.getCurrentPowerRole() == POWER_ROLE_SOURCE;
                      mSinkPower = status.getCurrentPowerRole() == POWER_ROLE_SINK;
                      mAudioAccessoryConnected = (status.getCurrentMode() == MODE_AUDIO_ACCESSORY);
                      mAudioAccessorySupported = port.isModeSupported(MODE_AUDIO_ACCESSORY);
                      // Ideally we want to see if PR_SWAP and DR_SWAP is supported.
                      // But, this should be suffice, since, all four combinations are only supported
                      // when PR_SWAP and DR_SWAP are supported.
                      mSupportsAllCombinations = status.isRoleCombinationSupported(
                              POWER_ROLE_SOURCE, DATA_ROLE_HOST)
                              && status.isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_HOST)
                              && status.isRoleCombinationSupported(POWER_ROLE_SOURCE,
                              DATA_ROLE_DEVICE)
                              && status.isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_DEVICE);
  
                      args.recycle();
                      updateUsbNotification(false);
                      if (mBootCompleted) {
                          if (mHostConnected || prevHostConnected) {
                              updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions));
                          }
                      } else {
                          mPendingBootBroadcast = true;
                      }
                      break;
                  case MSG_UPDATE_CHARGING_STATE:
                      mUsbCharging = (msg.arg1 == 1);
                      updateUsbNotification(false);
                      break;
                  case MSG_UPDATE_HOST_STATE:
                      Iterator devices = (Iterator) msg.obj;
                      boolean connected = (msg.arg1 == 1);
  
                      if (DEBUG) {
                          Slog.i(TAG, "HOST_STATE connected:" + connected);
                      }
  
                      mHideUsbNotification = false;
                      while (devices.hasNext()) {
                          Map.Entry pair = (Map.Entry) devices.next();
                          if (DEBUG) {
                              Slog.i(TAG, pair.getKey() + " = " + pair.getValue());
                          }
                          UsbDevice device = (UsbDevice) pair.getValue();
                          int configurationCount = device.getConfigurationCount() - 1;
                          while (configurationCount >= 0) {
                              UsbConfiguration config = device.getConfiguration(configurationCount);
                              configurationCount--;
                              int interfaceCount = config.getInterfaceCount() - 1;
                              while (interfaceCount >= 0) {
                                  UsbInterface intrface = config.getInterface(interfaceCount);
                                  interfaceCount--;
                                  if (sBlackListedInterfaces.contains(intrface.getInterfaceClass())) {
                                      mHideUsbNotification = true;
                                      break;
                                  }
                              }
                          }
                      }
                      updateUsbNotification(false);
                      break;
                  case MSG_ENABLE_ADB:
                      setAdbEnabled(msg.arg1 == 1);
                      break;
                  case MSG_SET_CURRENT_FUNCTIONS:
                      long functions = (Long) msg.obj;
                      setEnabledFunctions(functions, false);
                      break;
                  case MSG_SET_SCREEN_UNLOCKED_FUNCTIONS:
                      mScreenUnlockedFunctions = (Long) msg.obj;
                      if (mSettings != null) {
                          SharedPreferences.Editor editor = mSettings.edit();
                          editor.putString(String.format(Locale.ENGLISH, UNLOCKED_CONFIG_PREF,
                                  mCurrentUser),
                                  UsbManager.usbFunctionsToString(mScreenUnlockedFunctions));
                          editor.commit();
                      }
                      if (!mScreenLocked && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) {
                          // If the screen is unlocked, also set current functions.
                          setScreenUnlockedFunctions();
                      }
                      break;
                  case MSG_UPDATE_SCREEN_LOCK:
                      if (msg.arg1 == 1 == mScreenLocked) {
                          break;
                      }
                      mScreenLocked = msg.arg1 == 1;
                      if (!mBootCompleted) {
                          break;
                      }
                      if (mScreenLocked) {
                          if (!mConnected) {
                              setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
                          }
                      } else {
                          if (mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE
                                  && mCurrentFunctions == UsbManager.FUNCTION_NONE) {
                              // Set the screen unlocked functions if current function is charging.
                              setScreenUnlockedFunctions();
                          }
                      }
                      break;
                  case MSG_UPDATE_USER_RESTRICTIONS:
                      // Restart the USB stack if USB transfer is enabled but no longer allowed.
                      if (isUsbDataTransferActive(mCurrentFunctions) && !isUsbTransferAllowed()) {
                          setEnabledFunctions(UsbManager.FUNCTION_NONE, true);
                      }
                      break;
                  case MSG_SYSTEM_READY:
                      mNotificationManager = (NotificationManager)
                              mContext.getSystemService(Context.NOTIFICATION_SERVICE);
  
                      LocalServices.getService(
                              AdbManagerInternal.class).registerTransport(new AdbTransport(this));
  
                      // Ensure that the notification channels are set up
                      if (isTv()) {
                          // TV-specific notification channel
                          mNotificationManager.createNotificationChannel(
                                  new NotificationChannel(ADB_NOTIFICATION_CHANNEL_ID_TV,
                                          mContext.getString(
                                                  com.android.internal.R.string
                                                          .adb_debugging_notification_channel_tv),
                                          NotificationManager.IMPORTANCE_HIGH));
                      }
                      mSystemReady = true;
                      finishBoot();
                      break;
                  case MSG_LOCALE_CHANGED:
                      updateAdbNotification(true);
                      updateUsbNotification(true);
                      break;
                  case MSG_BOOT_COMPLETED:
                      mBootCompleted = true;
                      finishBoot();
                      break;
                  case MSG_USER_SWITCHED: {
                      if (mCurrentUser != msg.arg1) {
                          if (DEBUG) {
                              Slog.v(TAG, "Current user switched to " + msg.arg1);
                          }
                          mCurrentUser = msg.arg1;
                          mScreenLocked = true;
                          mScreenUnlockedFunctions = UsbManager.FUNCTION_NONE;
                          if (mSettings != null) {
                              mScreenUnlockedFunctions = UsbManager.usbFunctionsFromString(
                                      mSettings.getString(String.format(Locale.ENGLISH,
                                              UNLOCKED_CONFIG_PREF, mCurrentUser), ""));
                          }
                          setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
                      }
                      break;
                  }
                  case MSG_ACCESSORY_MODE_ENTER_TIMEOUT: {
                      if (DEBUG) {
                          Slog.v(TAG, "Accessory mode enter timeout: " + mConnected);
                      }
                      if (!mConnected || (mCurrentFunctions & UsbManager.FUNCTION_ACCESSORY) == 0) {
                          notifyAccessoryModeExit();
                      }
                      break;
                  }
              }
          }
  
          protected void finishBoot() {
              if (mBootCompleted && mCurrentUsbFunctionsReceived && mSystemReady) {
                  if (mPendingBootBroadcast) {
                      updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions));
                      mPendingBootBroadcast = false;
                  }
                  if (!mScreenLocked
                          && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) {
                      setScreenUnlockedFunctions();
                  } else {
                      setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
                  }
                  if (mCurrentAccessory != null) {
                      mUsbDeviceManager.getCurrentSettings().accessoryAttached(mCurrentAccessory);
                  }
  
                  updateUsbNotification(false);
                  updateAdbNotification(false);
                  updateUsbFunctions();
              }
          }
  
          protected boolean isUsbDataTransferActive(long functions) {
              return (functions & UsbManager.FUNCTION_MTP) != 0
                      || (functions & UsbManager.FUNCTION_PTP) != 0;
          }
  
          public UsbAccessory getCurrentAccessory() {
              return mCurrentAccessory;
          }
  
          protected void updateUsbNotification(boolean force) {
              if (mNotificationManager == null || !mUseUsbNotification
                      || ("0".equals(getSystemProperty("persist.charging.notify", "")))) {
                  return;
              }
  
              // Dont show the notification when connected to a USB peripheral
              // and the link does not support PR_SWAP and DR_SWAP
              if (mHideUsbNotification && !mSupportsAllCombinations) {
                  if (mUsbNotificationId != 0) {
                      mNotificationManager.cancelAsUser(null, mUsbNotificationId,
                              UserHandle.ALL);
                      mUsbNotificationId = 0;
                      Slog.d(TAG, "Clear notification");
                  }
                  return;
              }
  
              int id = 0;
              int titleRes = 0;
              Resources r = mContext.getResources();
              CharSequence message = r.getText(
                      com.android.internal.R.string.usb_notification_message);
              if (mAudioAccessoryConnected && !mAudioAccessorySupported) {
                  titleRes = com.android.internal.R.string.usb_unsupported_audio_accessory_title;
                  id = SystemMessage.NOTE_USB_AUDIO_ACCESSORY_NOT_SUPPORTED;
              } else if (mConnected) {
                  if (mCurrentFunctions == UsbManager.FUNCTION_MTP) {
                      titleRes = com.android.internal.R.string.usb_mtp_notification_title;
                      id = SystemMessage.NOTE_USB_MTP;
                  } else if (mCurrentFunctions == UsbManager.FUNCTION_PTP) {
                      titleRes = com.android.internal.R.string.usb_ptp_notification_title;
                      id = SystemMessage.NOTE_USB_PTP;
                  } else if (mCurrentFunctions == UsbManager.FUNCTION_MIDI) {
                      titleRes = com.android.internal.R.string.usb_midi_notification_title;
                      id = SystemMessage.NOTE_USB_MIDI;
                  } else if (mCurrentFunctions == UsbManager.FUNCTION_RNDIS) {
                      titleRes = com.android.internal.R.string.usb_tether_notification_title;
                      id = SystemMessage.NOTE_USB_TETHER;
                  } else if (mCurrentFunctions == UsbManager.FUNCTION_ACCESSORY) {
                      titleRes = com.android.internal.R.string.usb_accessory_notification_title;
                      id = SystemMessage.NOTE_USB_ACCESSORY;
                  }
                  if (mSourcePower) {
                      if (titleRes != 0) {
                          message = r.getText(
                                  com.android.internal.R.string.usb_power_notification_message);
                      } else {
                          titleRes = com.android.internal.R.string.usb_supplying_notification_title;
                          id = SystemMessage.NOTE_USB_SUPPLYING;
                      }
                  } else if (titleRes == 0) {
                      titleRes = com.android.internal.R.string.usb_charging_notification_title;
                      id = SystemMessage.NOTE_USB_CHARGING;
                  }
              } else if (mSourcePower) {
                  titleRes = com.android.internal.R.string.usb_supplying_notification_title;
                  id = SystemMessage.NOTE_USB_SUPPLYING;
              } else if (mHostConnected && mSinkPower && mUsbCharging) {
                  titleRes = com.android.internal.R.string.usb_charging_notification_title;
                  id = SystemMessage.NOTE_USB_CHARGING;
              }
              if (id != mUsbNotificationId || force) {
                  // clear notification if title needs changing
                  if (mUsbNotificationId != 0) {
                      mNotificationManager.cancelAsUser(null, mUsbNotificationId,
                              UserHandle.ALL);
                      Slog.d(TAG, "Clear notification");
                      mUsbNotificationId = 0;
                  }
                  // Not relevant for automotive.
                  if (mContext.getPackageManager().hasSystemFeature(
                          PackageManager.FEATURE_AUTOMOTIVE)
                          && id == SystemMessage.NOTE_USB_CHARGING) {
                      mUsbNotificationId = 0;
                      return;
                  }
  
                  if (id != 0) {
                      CharSequence title = r.getText(titleRes);
                      PendingIntent pi;
                      String channel;
  
                      if (titleRes
                              != com.android.internal.R.string
                              .usb_unsupported_audio_accessory_title) {
                          Intent intent = Intent.makeRestartActivityTask(
                                  new ComponentName("com.android.settings",
                                          "com.android.settings.Settings$UsbDetailsActivity"));
                          pi = PendingIntent.getActivityAsUser(mContext, 0,
                                  intent, 0, null, UserHandle.CURRENT);
                          channel = SystemNotificationChannels.USB;
                      } else {
                          final Intent intent = new Intent();
                          intent.setClassName("com.android.settings",
                                  "com.android.settings.HelpTrampoline");
                          intent.putExtra(Intent.EXTRA_TEXT,
                                  "help_url_audio_accessory_not_supported");
  
                          if (mContext.getPackageManager().resolveActivity(intent, 0) != null) {
                              pi = PendingIntent.getActivity(mContext, 0, intent, 0);
                          } else {
                              pi = null;
                          }
  
                          channel = SystemNotificationChannels.ALERTS;
                          message = r.getText(
                                  com.android.internal.R.string
                                          .usb_unsupported_audio_accessory_message);
                      }
  
                      Notification.Builder builder = new Notification.Builder(mContext, channel)
                              .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
                              .setWhen(0)
                              .setOngoing(true)
                              .setTicker(title)
                              .setDefaults(0)  // please be quiet
                              .setColor(mContext.getColor(
                                      com.android.internal.R.color
                                              .system_notification_accent_color))
                              .setContentTitle(title)
                              .setContentText(message)
                              .setContentIntent(pi)
                              .setVisibility(Notification.VISIBILITY_PUBLIC);
  
                      if (titleRes
                              == com.android.internal.R.string
                              .usb_unsupported_audio_accessory_title) {
                          builder.setStyle(new Notification.BigTextStyle()
                                  .bigText(message));
                      }
                      Notification notification = builder.build();
  
                      mNotificationManager.notifyAsUser(null, id, notification,
                              UserHandle.ALL);
                      Slog.d(TAG, "push notification:" + title);
                      mUsbNotificationId = id;
                  }
              }
          }
  
          protected boolean isAdbEnabled() {
              return LocalServices.getService(AdbManagerInternal.class).isAdbEnabled();
          }
  
          protected void updateAdbNotification(boolean force) {
              if (mNotificationManager == null) return;
              final int id = SystemMessage.NOTE_ADB_ACTIVE;
              final int titleRes = com.android.internal.R.string.adb_active_notification_title;
  
              if (isAdbEnabled() && mConnected) {
                  if ("0".equals(getSystemProperty("persist.adb.notify", ""))) return;
  
                  if (force && mAdbNotificationShown) {
                      mAdbNotificationShown = false;
                      mNotificationManager.cancelAsUser(null, id, UserHandle.ALL);
                  }
  
                  if (!mAdbNotificationShown) {
                      Resources r = mContext.getResources();
                      CharSequence title = r.getText(titleRes);
                      CharSequence message = r.getText(
                              com.android.internal.R.string.adb_active_notification_message);
  
                      Intent intent = new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS);
                      intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                              | Intent.FLAG_ACTIVITY_CLEAR_TASK);
                      PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0,
                              intent, 0, null, UserHandle.CURRENT);
  
                      Notification notification =
                              new Notification.Builder(mContext, SystemNotificationChannels.DEVELOPER)
                                      .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
                                      .setWhen(0)
                                      .setOngoing(true)
                                      .setTicker(title)
                                      .setDefaults(0)  // please be quiet
                                      .setColor(mContext.getColor(
                                              com.android.internal.R.color
                                                      .system_notification_accent_color))
                                      .setContentTitle(title)
                                      .setContentText(message)
                                      .setContentIntent(pi)
                                      .setVisibility(Notification.VISIBILITY_PUBLIC)
                                      .extend(new Notification.TvExtender()
                                              .setChannelId(ADB_NOTIFICATION_CHANNEL_ID_TV))
                                      .build();
                      mAdbNotificationShown = true;
                      mNotificationManager.notifyAsUser(null, id, notification,
                              UserHandle.ALL);
                  }
              } else if (mAdbNotificationShown) {
                  mAdbNotificationShown = false;
                  mNotificationManager.cancelAsUser(null, id, UserHandle.ALL);
              }
          }
  
          private boolean isTv() {
              return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
          }
  
          protected long getChargingFunctions() {
              // if ADB is enabled, reset functions to ADB
              // else enable MTP as usual.
              if (isAdbEnabled()) {
                  return UsbManager.FUNCTION_ADB;
              } else {
                  return UsbManager.FUNCTION_MTP;
              }
          }
  
          protected void setSystemProperty(String prop, String val) {
              SystemProperties.set(prop, val);
          }
  
          protected String getSystemProperty(String prop, String def) {
              return SystemProperties.get(prop, def);
          }
  
          protected void putGlobalSettings(ContentResolver contentResolver, String setting, int val) {
              Settings.Global.putInt(contentResolver, setting, val);
          }
  
          public long getEnabledFunctions() {
              return mCurrentFunctions;
          }
  
          public long getScreenUnlockedFunctions() {
              return mScreenUnlockedFunctions;
          }
  
          /**
           * Dump a functions mask either as proto-enums (if dumping to proto) or a string (if dumping
           * to a print writer)
           */
          private void dumpFunctions(DualDumpOutputStream dump, String idName, long id,
                  long functions) {
              // UsbHandlerProto.UsbFunction matches GadgetFunction
              for (int i = 0; i < 63; i++) {
                  if ((functions & (1L << i)) != 0) {
                      if (dump.isProto()) {
                          dump.write(idName, id, 1L << i);
                      } else {
                          dump.write(idName, id, GadgetFunction.toString(1L << i));
                      }
                  }
              }
          }
  
          public void dump(DualDumpOutputStream dump, String idName, long id) {
              long token = dump.start(idName, id);
  
              dumpFunctions(dump, "current_functions", UsbHandlerProto.CURRENT_FUNCTIONS,
                      mCurrentFunctions);
              dump.write("current_functions_applied", UsbHandlerProto.CURRENT_FUNCTIONS_APPLIED,
                      mCurrentFunctionsApplied);
              dumpFunctions(dump, "screen_unlocked_functions",
                      UsbHandlerProto.SCREEN_UNLOCKED_FUNCTIONS, mScreenUnlockedFunctions);
              dump.write("screen_locked", UsbHandlerProto.SCREEN_LOCKED, mScreenLocked);
              dump.write("connected", UsbHandlerProto.CONNECTED, mConnected);
              dump.write("configured", UsbHandlerProto.CONFIGURED, mConfigured);
              if (mCurrentAccessory != null) {
                  writeAccessory(dump, "current_accessory", UsbHandlerProto.CURRENT_ACCESSORY,
                          mCurrentAccessory);
              }
              dump.write("host_connected", UsbHandlerProto.HOST_CONNECTED, mHostConnected);
              dump.write("source_power", UsbHandlerProto.SOURCE_POWER, mSourcePower);
              dump.write("sink_power", UsbHandlerProto.SINK_POWER, mSinkPower);
              dump.write("usb_charging", UsbHandlerProto.USB_CHARGING, mUsbCharging);
              dump.write("hide_usb_notification", UsbHandlerProto.HIDE_USB_NOTIFICATION,
                      mHideUsbNotification);
              dump.write("audio_accessory_connected", UsbHandlerProto.AUDIO_ACCESSORY_CONNECTED,
                      mAudioAccessoryConnected);
  
              try {
                  writeStringIfNotNull(dump, "kernel_state", UsbHandlerProto.KERNEL_STATE,
                          FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim());
              } catch (Exception e) {
                  Slog.e(TAG, "Could not read kernel state", e);
              }
  
              try {
                  writeStringIfNotNull(dump, "kernel_function_list",
                          UsbHandlerProto.KERNEL_FUNCTION_LIST,
                          FileUtils.readTextFile(new File(FUNCTIONS_PATH), 0, null).trim());
              } catch (Exception e) {
                  Slog.e(TAG, "Could not read kernel function list", e);
              }
  
              dump.end(token);
          }
  
          /**
           * Evaluates USB function policies and applies the change accordingly.
           */
          protected abstract void setEnabledFunctions(long functions, boolean forceRestart);
      }
  
      private static final class UsbHandlerLegacy extends UsbHandler {
          /**
           * The non-persistent property which stores the current USB settings.
           */
          private static final String USB_CONFIG_PROPERTY = "sys.usb.config";
  
          /**
           * The non-persistent property which stores the current USB actual state.
           */
          private static final String USB_STATE_PROPERTY = "sys.usb.state";
  
          private HashMap<String, HashMap<String, Pair<String, String>>> mOemModeMap;
          private String mCurrentOemFunctions;
          private String mCurrentFunctionsStr;
          private boolean mUsbDataUnlocked;
  
          UsbHandlerLegacy(Looper looper, Context context, UsbDeviceManager deviceManager,
                  UsbAlsaManager alsaManager, UsbSettingsManager settingsManager) {
              super(looper, context, deviceManager, alsaManager, settingsManager);
              try {
                  readOemUsbOverrideConfig(context);
                  // Restore default functions.
                  mCurrentOemFunctions = getSystemProperty(getPersistProp(false),
                          UsbManager.USB_FUNCTION_NONE);
                  if (isNormalBoot()) {
                      mCurrentFunctionsStr = getSystemProperty(USB_CONFIG_PROPERTY,
                              UsbManager.USB_FUNCTION_NONE);
                      mCurrentFunctionsApplied = mCurrentFunctionsStr.equals(
                              getSystemProperty(USB_STATE_PROPERTY, UsbManager.USB_FUNCTION_NONE));
                  } else {
                      mCurrentFunctionsStr = getSystemProperty(getPersistProp(true),
                              UsbManager.USB_FUNCTION_NONE);
                      mCurrentFunctionsApplied = getSystemProperty(USB_CONFIG_PROPERTY,
                              UsbManager.USB_FUNCTION_NONE).equals(
                              getSystemProperty(USB_STATE_PROPERTY, UsbManager.USB_FUNCTION_NONE));
                  }
                  mCurrentFunctions = UsbManager.FUNCTION_NONE;
                  mCurrentUsbFunctionsReceived = true;
  
                  String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim();
                  updateState(state);
              } catch (Exception e) {
                  Slog.e(TAG, "Error initializing UsbHandler", e);
              }
          }
  
          private void readOemUsbOverrideConfig(Context context) {
              String[] configList = context.getResources().getStringArray(
                      com.android.internal.R.array.config_oemUsbModeOverride);
  
              if (configList != null) {
                  for (String config : configList) {
                      String[] items = config.split(":");
                      if (items.length == 3 || items.length == 4) {
                          if (mOemModeMap == null) {
                              mOemModeMap = new HashMap<>();
                          }
                          HashMap<String, Pair<String, String>> overrideMap =
                                  mOemModeMap.get(items[0]);
                          if (overrideMap == null) {
                              overrideMap = new HashMap<>();
                              mOemModeMap.put(items[0], overrideMap);
                          }
  
                          // Favoring the first combination if duplicate exists
                          if (!overrideMap.containsKey(items[1])) {
                              if (items.length == 3) {
                                  overrideMap.put(items[1], new Pair<>(items[2], ""));
                              } else {
                                  overrideMap.put(items[1], new Pair<>(items[2], items[3]));
                              }
                          }
                      }
                  }
              }
          }
  
          private String applyOemOverrideFunction(String usbFunctions) {
              if ((usbFunctions == null) || (mOemModeMap == null)) {
                  return usbFunctions;
              }
  
              String bootMode = getSystemProperty(BOOT_MODE_PROPERTY, "unknown");
              Slog.d(TAG, "applyOemOverride usbfunctions=" + usbFunctions + " bootmode=" + bootMode);
  
              Map<String, Pair<String, String>> overridesMap =
                      mOemModeMap.get(bootMode);
              // Check to ensure that the oem is not overriding in the normal
              // boot mode
              if (overridesMap != null && !(bootMode.equals(NORMAL_BOOT)
                      || bootMode.equals("unknown"))) {
                  Pair<String, String> overrideFunctions =
                          overridesMap.get(usbFunctions);
                  if (overrideFunctions != null) {
                      Slog.d(TAG, "OEM USB override: " + usbFunctions
                              + " ==> " + overrideFunctions.first
                              + " persist across reboot "
                              + overrideFunctions.second);
                      if (!overrideFunctions.second.equals("")) {
                          String newFunction;
                          if (isAdbEnabled()) {
                              newFunction = addFunction(overrideFunctions.second,
                                      UsbManager.USB_FUNCTION_ADB);
                          } else {
                              newFunction = overrideFunctions.second;
                          }
                          Slog.d(TAG, "OEM USB override persisting: " + newFunction + "in prop: "
                                  + getPersistProp(false));
                          setSystemProperty(getPersistProp(false), newFunction);
                      }
                      return overrideFunctions.first;
                  } else if (isAdbEnabled()) {
                      String newFunction = addFunction(UsbManager.USB_FUNCTION_NONE,
                              UsbManager.USB_FUNCTION_ADB);
                      setSystemProperty(getPersistProp(false), newFunction);
                  } else {
                      setSystemProperty(getPersistProp(false), UsbManager.USB_FUNCTION_NONE);
                  }
              }
              // return passed in functions as is.
              return usbFunctions;
          }
  
          private boolean waitForState(String state) {
              // wait for the transition to complete.
              // give up after 1 second.
              String value = null;
              for (int i = 0; i < 20; i++) {
                  // State transition is done when sys.usb.state is set to the new configuration
                  value = getSystemProperty(USB_STATE_PROPERTY, "");
                  if (state.equals(value)) return true;
                  SystemClock.sleep(50);
              }
              Slog.e(TAG, "waitForState(" + state + ") FAILED: got " + value);
              return false;
          }
  
          private void setUsbConfig(String config) {
              if (DEBUG) Slog.d(TAG, "setUsbConfig(" + config + ")");
              /**
               * set the new configuration
               * we always set it due to b/23631400, where adbd was getting killed
               * and not restarted due to property timeouts on some devices
               */
              setSystemProperty(USB_CONFIG_PROPERTY, config);
          }
  
          @Override
          protected void setEnabledFunctions(long usbFunctions, boolean forceRestart) {
              boolean usbDataUnlocked = isUsbDataTransferActive(usbFunctions);
              if (DEBUG) {
                  Slog.d(TAG, "setEnabledFunctions functions=" + usbFunctions + ", "
                          + "forceRestart=" + forceRestart + ", usbDataUnlocked=" + usbDataUnlocked);
              }
  
              if (usbDataUnlocked != mUsbDataUnlocked) {
                  mUsbDataUnlocked = usbDataUnlocked;
                  updateUsbNotification(false);
                  forceRestart = true;
              }
  
              /**
               * Try to set the enabled functions.
               */
              final long oldFunctions = mCurrentFunctions;
              final boolean oldFunctionsApplied = mCurrentFunctionsApplied;
              if (trySetEnabledFunctions(usbFunctions, forceRestart)) {
                  return;
              }
  
              /**
               * Didn't work.  Try to revert changes.
               * We always reapply the policy in case certain constraints changed such as
               * user restrictions independently of any other new functions we were
               * trying to activate.
               */
              if (oldFunctionsApplied && oldFunctions != usbFunctions) {
                  Slog.e(TAG, "Failsafe 1: Restoring previous USB functions.");
                  if (trySetEnabledFunctions(oldFunctions, false)) {
                      return;
                  }
              }
  
              /**
               * Still didn't work.  Try to restore the default functions.
               */
              Slog.e(TAG, "Failsafe 2: Restoring default USB functions.");
              if (trySetEnabledFunctions(UsbManager.FUNCTION_NONE, false)) {
                  return;
              }
  
              /**
               * Now we're desperate.  Ignore the default functions.
               * Try to get ADB working if enabled.
               */
              Slog.e(TAG, "Failsafe 3: Restoring empty function list (with ADB if enabled).");
              if (trySetEnabledFunctions(UsbManager.FUNCTION_NONE, false)) {
                  return;
              }
  
              /**
               * Ouch.
               */
              Slog.e(TAG, "Unable to set any USB functions!");
          }
  
          private boolean isNormalBoot() {
              String bootMode = getSystemProperty(BOOT_MODE_PROPERTY, "unknown");
              return bootMode.equals(NORMAL_BOOT) || bootMode.equals("unknown");
          }
  
          protected String applyAdbFunction(String functions) {
              // Do not pass null pointer to the UsbManager.
              // There isn't a check there.
              if (functions == null) {
                  functions = "";
              }
              if (isAdbEnabled()) {
                  functions = addFunction(functions, UsbManager.USB_FUNCTION_ADB);
              } else {
                  functions = removeFunction(functions, UsbManager.USB_FUNCTION_ADB);
              }
              return functions;
          }
  
          private boolean trySetEnabledFunctions(long usbFunctions, boolean forceRestart) {
              String functions = null;
              if (usbFunctions != UsbManager.FUNCTION_NONE) {
                  functions = UsbManager.usbFunctionsToString(usbFunctions);
              }
              mCurrentFunctions = usbFunctions;
              if (functions == null || applyAdbFunction(functions)
                      .equals(UsbManager.USB_FUNCTION_NONE)) {
                  functions = UsbManager.usbFunctionsToString(getChargingFunctions());
              }
              functions = applyAdbFunction(functions);
  
              String oemFunctions = applyOemOverrideFunction(functions);
  
              if (!isNormalBoot() && !mCurrentFunctionsStr.equals(functions)) {
                  setSystemProperty(getPersistProp(true), functions);
              }
  
              if ((!functions.equals(oemFunctions)
                      && !mCurrentOemFunctions.equals(oemFunctions))
                      || !mCurrentFunctionsStr.equals(functions)
                      || !mCurrentFunctionsApplied
                      || forceRestart) {
                  Slog.i(TAG, "Setting USB config to " + functions);
                  mCurrentFunctionsStr = functions;
                  mCurrentOemFunctions = oemFunctions;
                  mCurrentFunctionsApplied = false;
  
                  /**
                   * Kick the USB stack to close existing connections.
                   */
                  setUsbConfig(UsbManager.USB_FUNCTION_NONE);
  
                  if (!waitForState(UsbManager.USB_FUNCTION_NONE)) {
                      Slog.e(TAG, "Failed to kick USB config");
                      return false;
                  }
  
                  /**
                   * Set the new USB configuration.
                   */
                  setUsbConfig(oemFunctions);
  
                  if (mBootCompleted
                          && (containsFunction(functions, UsbManager.USB_FUNCTION_MTP)
                          || containsFunction(functions, UsbManager.USB_FUNCTION_PTP))) {
                      /**
                       * Start up dependent services.
                       */
                      updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions));
                  }
  
                  if (!waitForState(oemFunctions)) {
                      Slog.e(TAG, "Failed to switch USB config to " + functions);
                      return false;
                  }
  
                  mCurrentFunctionsApplied = true;
              }
              return true;
          }
  
          private String getPersistProp(boolean functions) {
              String bootMode = getSystemProperty(BOOT_MODE_PROPERTY, "unknown");
              String persistProp = USB_PERSISTENT_CONFIG_PROPERTY;
              if (!(bootMode.equals(NORMAL_BOOT) || bootMode.equals("unknown"))) {
                  if (functions) {
                      persistProp = "persist.sys.usb." + bootMode + ".func";
                  } else {
                      persistProp = "persist.sys.usb." + bootMode + ".config";
                  }
              }
              return persistProp;
          }
  
          private static String addFunction(String functions, String function) {
              if (UsbManager.USB_FUNCTION_NONE.equals(functions)) {
                  return function;
              }
              if (!containsFunction(functions, function)) {
                  if (functions.length() > 0) {
                      functions += ",";
                  }
                  functions += function;
              }
              return functions;
          }
  
          private static String removeFunction(String functions, String function) {
              String[] split = functions.split(",");
              for (int i = 0; i < split.length; i++) {
                  if (function.equals(split[i])) {
                      split[i] = null;
                  }
              }
              if (split.length == 1 && split[0] == null) {
                  return UsbManager.USB_FUNCTION_NONE;
              }
              StringBuilder builder = new StringBuilder();
              for (int i = 0; i < split.length; i++) {
                  String s = split[i];
                  if (s != null) {
                      if (builder.length() > 0) {
                          builder.append(",");
                      }
                      builder.append(s);
                  }
              }
              return builder.toString();
          }
  
          static boolean containsFunction(String functions, String function) {
              int index = functions.indexOf(function);
              if (index < 0) return false;
              if (index > 0 && functions.charAt(index - 1) != ',') return false;
              int charAfter = index + function.length();
              if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false;
              return true;
          }
      }
  
      private static final class UsbHandlerHal extends UsbHandler {
  
          /**
           * Proxy object for the usb gadget hal daemon.
           */
          @GuardedBy("mGadgetProxyLock")
          private IUsbGadget mGadgetProxy;
  
          private final Object mGadgetProxyLock = new Object();
  
          /**
           * Cookie sent for usb gadget hal death notification.
           */
          private static final int USB_GADGET_HAL_DEATH_COOKIE = 2000;
  
          /**
           * Keeps track of the latest setCurrentUsbFunctions request number.
           */
          private int mCurrentRequest = 0;
  
          /**
           * The maximum time for which the UsbDeviceManager would wait once
           * setCurrentUsbFunctions is called.
           */
          private static final int SET_FUNCTIONS_TIMEOUT_MS = 3000;
  
          /**
           * Conseration leeway to make sure that the hal callback arrives before
           * SET_FUNCTIONS_TIMEOUT_MS expires. If the callback does not arrive
           * within SET_FUNCTIONS_TIMEOUT_MS, UsbDeviceManager retries enabling
           * default functions.
           */
          private static final int SET_FUNCTIONS_LEEWAY_MS = 500;
  
          /**
           * While switching functions, a disconnect is excpect as the usb gadget
           * us torn down and brought back up. Wait for SET_FUNCTIONS_TIMEOUT_MS +
           * ENUMERATION_TIME_OUT_MS before switching back to default fumctions when
           * switching functions.
           */
          private static final int ENUMERATION_TIME_OUT_MS = 2000;
  
          /**
           * Command to start native service.
           */
          protected static final String CTL_START = "ctl.start";
  
          /**
           * Command to start native service.
           */
          protected static final String CTL_STOP = "ctl.stop";
  
          /**
           * Adb native daemon.
           */
          protected static final String ADBD = "adbd";
  
          /**
           * Gadget HAL fully qualified instance name for registering for ServiceNotification.
           */
          protected static final String GADGET_HAL_FQ_NAME =
                  "android.hardware.usb.gadget@1.0::IUsbGadget";
  
          protected boolean mCurrentUsbFunctionsRequested;
  
          UsbHandlerHal(Looper looper, Context context, UsbDeviceManager deviceManager,
                  UsbAlsaManager alsaManager, UsbSettingsManager settingsManager) {
              super(looper, context, deviceManager, alsaManager, settingsManager);
              try {
                  ServiceNotification serviceNotification = new ServiceNotification();
  
                  boolean ret = IServiceManager.getService()
                          .registerForNotifications(GADGET_HAL_FQ_NAME, "", serviceNotification);
                  if (!ret) {
                      Slog.e(TAG, "Failed to register usb gadget service start notification");
                      return;
                  }
  
                  synchronized (mGadgetProxyLock) {
                      mGadgetProxy = IUsbGadget.getService(true);
                      mGadgetProxy.linkToDeath(new UsbGadgetDeathRecipient(),
                              USB_GADGET_HAL_DEATH_COOKIE);
                      mCurrentFunctions = UsbManager.FUNCTION_NONE;
                      mCurrentUsbFunctionsRequested = true;
                      mGadgetProxy.getCurrentUsbFunctions(new UsbGadgetCallback());
                  }
                  String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim();
                  updateState(state);
              } catch (NoSuchElementException e) {
                  Slog.e(TAG, "Usb gadget hal not found", e);
              } catch (RemoteException e) {
                  Slog.e(TAG, "Usb Gadget hal not responding", e);
              } catch (Exception e) {
                  Slog.e(TAG, "Error initializing UsbHandler", e);
              }
          }
  
  
          final class UsbGadgetDeathRecipient implements HwBinder.DeathRecipient {
              @Override
              public void serviceDied(long cookie) {
                  if (cookie == USB_GADGET_HAL_DEATH_COOKIE) {
                      Slog.e(TAG, "Usb Gadget hal service died cookie: " + cookie);
                      synchronized (mGadgetProxyLock) {
                          mGadgetProxy = null;
                      }
                  }
              }
          }
  
          final class ServiceNotification extends IServiceNotification.Stub {
              @Override
              public void onRegistration(String fqName, String name, boolean preexisting) {
                  Slog.i(TAG, "Usb gadget hal service started " + fqName + " " + name);
                  if (!fqName.equals(GADGET_HAL_FQ_NAME)) {
                      Slog.e(TAG, "fqName does not match");
                      return;
                  }
  
                  sendMessage(MSG_GADGET_HAL_REGISTERED, preexisting);
              }
          }
  
          @Override
          public void handleMessage(Message msg) {
              switch (msg.what) {
                  case MSG_SET_CHARGING_FUNCTIONS:
                      setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
                      break;
                  case MSG_SET_FUNCTIONS_TIMEOUT:
                      Slog.e(TAG, "Set functions timed out! no reply from usb hal");
                      if (msg.arg1 != 1) {
                          setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
                      }
                      break;
                  case MSG_GET_CURRENT_USB_FUNCTIONS:
                      Slog.e(TAG, "prcessing MSG_GET_CURRENT_USB_FUNCTIONS");
                      mCurrentUsbFunctionsReceived = true;
  
                      if (mCurrentUsbFunctionsRequested) {
                          Slog.e(TAG, "updating mCurrentFunctions");
                          // Mask out adb, since it is stored in mAdbEnabled
                          mCurrentFunctions = ((Long) msg.obj) & ~UsbManager.FUNCTION_ADB;
                          Slog.e(TAG,
                                  "mCurrentFunctions:" + mCurrentFunctions + "applied:" + msg.arg1);
                          mCurrentFunctionsApplied = msg.arg1 == 1;
                      }
                      finishBoot();
                      break;
                  case MSG_FUNCTION_SWITCH_TIMEOUT:
                      /**
                       * Dont force to default when the configuration is already set to default.
                       */
                      if (msg.arg1 != 1) {
                          setEnabledFunctions(UsbManager.FUNCTION_NONE, !isAdbEnabled());
                      }
                      break;
                  case MSG_GADGET_HAL_REGISTERED:
                      boolean preexisting = msg.arg1 == 1;
                      synchronized (mGadgetProxyLock) {
                          try {
                              mGadgetProxy = IUsbGadget.getService();
                              mGadgetProxy.linkToDeath(new UsbGadgetDeathRecipient(),
                                      USB_GADGET_HAL_DEATH_COOKIE);
                              if (!mCurrentFunctionsApplied && !preexisting) {
                                  setEnabledFunctions(mCurrentFunctions, false);
                              }
                          } catch (NoSuchElementException e) {
                              Slog.e(TAG, "Usb gadget hal not found", e);
                          } catch (RemoteException e) {
                              Slog.e(TAG, "Usb Gadget hal not responding", e);
                          }
                      }
                      break;
                  default:
                      super.handleMessage(msg);
              }
          }
  
          private class UsbGadgetCallback extends IUsbGadgetCallback.Stub {
              int mRequest;
              long mFunctions;
              boolean mChargingFunctions;
  
              UsbGadgetCallback() {
              }
  
              UsbGadgetCallback(int request, long functions,
                      boolean chargingFunctions) {
                  mRequest = request;
                  mFunctions = functions;
                  mChargingFunctions = chargingFunctions;
              }
  
              @Override
              public void setCurrentUsbFunctionsCb(long functions,
                      int status) {
                  /**
                   * Callback called for a previous setCurrenUsbFunction
                   */
                  if ((mCurrentRequest != mRequest) || !hasMessages(MSG_SET_FUNCTIONS_TIMEOUT)
                          || (mFunctions != functions)) {
                      return;
                  }
  
                  removeMessages(MSG_SET_FUNCTIONS_TIMEOUT);
                  Slog.e(TAG, "notifyCurrentFunction request:" + mRequest + " status:" + status);
                  if (status == Status.SUCCESS) {
                      mCurrentFunctionsApplied = true;
                  } else if (!mChargingFunctions) {
                      Slog.e(TAG, "Setting default fuctions");
                      sendEmptyMessage(MSG_SET_CHARGING_FUNCTIONS);
                  }
              }
  
              @Override
              public void getCurrentUsbFunctionsCb(long functions,
                      int status) {
                  sendMessage(MSG_GET_CURRENT_USB_FUNCTIONS, functions,
                          status == Status.FUNCTIONS_APPLIED);
              }
          }
  
          private void setUsbConfig(long config, boolean chargingFunctions) {
              if (true) Slog.d(TAG, "setUsbConfig(" + config + ") request:" + ++mCurrentRequest);
              /**
               * Cancel any ongoing requests, if present.
               */
              removeMessages(MSG_FUNCTION_SWITCH_TIMEOUT);
              removeMessages(MSG_SET_FUNCTIONS_TIMEOUT);
              removeMessages(MSG_SET_CHARGING_FUNCTIONS);
  
              synchronized (mGadgetProxyLock) {
                  if (mGadgetProxy == null) {
                      Slog.e(TAG, "setUsbConfig mGadgetProxy is null");
                      return;
                  }
                  try {
                      if ((config & UsbManager.FUNCTION_ADB) != 0) {
                          /**
                           * Start adbd if ADB function is included in the configuration.
                           */
                          setSystemProperty(CTL_START, ADBD);
                      } else {
                          /**
                           * Stop adbd otherwise.
                           */
                          setSystemProperty(CTL_STOP, ADBD);
                      }
                      UsbGadgetCallback usbGadgetCallback = new UsbGadgetCallback(mCurrentRequest,
                              config, chargingFunctions);
                      mGadgetProxy.setCurrentUsbFunctions(config, usbGadgetCallback,
                              SET_FUNCTIONS_TIMEOUT_MS - SET_FUNCTIONS_LEEWAY_MS);
                      sendMessageDelayed(MSG_SET_FUNCTIONS_TIMEOUT, chargingFunctions,
                              SET_FUNCTIONS_TIMEOUT_MS);
                      sendMessageDelayed(MSG_FUNCTION_SWITCH_TIMEOUT, chargingFunctions,
                              SET_FUNCTIONS_TIMEOUT_MS + ENUMERATION_TIME_OUT_MS);
                      if (DEBUG) Slog.d(TAG, "timeout message queued");
                  } catch (RemoteException e) {
                      Slog.e(TAG, "Remoteexception while calling setCurrentUsbFunctions", e);
                  }
              }
          }
  
          @Override
          protected void setEnabledFunctions(long functions, boolean forceRestart) {
              if (DEBUG) {
                  Slog.d(TAG, "setEnabledFunctions functions=" + functions + ", "
                          + "forceRestart=" + forceRestart);
              }
              if (mCurrentFunctions != functions
                      || !mCurrentFunctionsApplied
                      || forceRestart) {
                  Slog.i(TAG, "Setting USB config to " + UsbManager.usbFunctionsToString(functions));
                  mCurrentFunctions = functions;
                  mCurrentFunctionsApplied = false;
                  // set the flag to false as that would be stale value
                  mCurrentUsbFunctionsRequested = false;
  
                  boolean chargingFunctions = functions == UsbManager.FUNCTION_NONE;
                  functions = getAppliedFunctions(functions);
  
                  // Set the new USB configuration.
                  setUsbConfig(functions, chargingFunctions);
  
                  if (mBootCompleted && isUsbDataTransferActive(functions)) {
                      // Start up dependent services.
                      updateUsbStateBroadcastIfNeeded(functions);
                  }
              }
          }
      }
  
      /* returns the currently attached USB accessory */
      public UsbAccessory getCurrentAccessory() {
          return mHandler.getCurrentAccessory();
      }
  
      /**
       * opens the currently attached USB accessory.
       *
       * @param accessory accessory to be openened.
       * @param uid Uid of the caller
       */
      public ParcelFileDescriptor openAccessory(UsbAccessory accessory,
              UsbUserSettingsManager settings, int uid) {
          UsbAccessory currentAccessory = mHandler.getCurrentAccessory();
          if (currentAccessory == null) {
              throw new IllegalArgumentException("no accessory attached");
          }
          if (!currentAccessory.equals(accessory)) {
              String error = accessory.toString()
                      + " does not match current accessory "
                      + currentAccessory;
              throw new IllegalArgumentException(error);
          }
          settings.checkPermission(accessory, uid);
          return nativeOpenAccessory();
      }
  
      public long getCurrentFunctions() {
          return mHandler.getEnabledFunctions();
      }
  
      /**
       * Returns a dup of the control file descriptor for the given function.
       */
      public ParcelFileDescriptor getControlFd(long usbFunction) {
          FileDescriptor fd = mControlFds.get(usbFunction);
          if (fd == null) {
              return null;
          }
          try {
              return ParcelFileDescriptor.dup(fd);
          } catch (IOException e) {
              Slog.e(TAG, "Could not dup fd for " + usbFunction);
              return null;
          }
      }
  
      public long getScreenUnlockedFunctions() {
          return mHandler.getScreenUnlockedFunctions();
      }
  
      /**
       * Adds function to the current USB configuration.
       *
       * @param functions The functions to set, or empty to set the charging function.
       */
      public void setCurrentFunctions(long functions) {
          if (DEBUG) {
              Slog.d(TAG, "setCurrentFunctions(" + UsbManager.usbFunctionsToString(functions) + ")");
          }
          if (functions == UsbManager.FUNCTION_NONE) {
              MetricsLogger.action(mContext, MetricsEvent.ACTION_USB_CONFIG_CHARGING);
          } else if (functions == UsbManager.FUNCTION_MTP) {
              MetricsLogger.action(mContext, MetricsEvent.ACTION_USB_CONFIG_MTP);
          } else if (functions == UsbManager.FUNCTION_PTP) {
              MetricsLogger.action(mContext, MetricsEvent.ACTION_USB_CONFIG_PTP);
          } else if (functions == UsbManager.FUNCTION_MIDI) {
              MetricsLogger.action(mContext, MetricsEvent.ACTION_USB_CONFIG_MIDI);
          } else if (functions == UsbManager.FUNCTION_RNDIS) {
              MetricsLogger.action(mContext, MetricsEvent.ACTION_USB_CONFIG_RNDIS);
          } else if (functions == UsbManager.FUNCTION_ACCESSORY) {
              MetricsLogger.action(mContext, MetricsEvent.ACTION_USB_CONFIG_ACCESSORY);
          }
          mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions);
      }
  
      /**
       * Sets the functions which are set when the screen is unlocked.
       *
       * @param functions Functions to set.
       */
      public void setScreenUnlockedFunctions(long functions) {
          if (DEBUG) {
              Slog.d(TAG, "setScreenUnlockedFunctions("
                      + UsbManager.usbFunctionsToString(functions) + ")");
          }
          mHandler.sendMessage(MSG_SET_SCREEN_UNLOCKED_FUNCTIONS, functions);
      }
  
      private void onAdbEnabled(boolean enabled) {
          mHandler.sendMessage(MSG_ENABLE_ADB, enabled);
      }
  
      /**
       * Write the state to a dump stream.
       */
      public void dump(DualDumpOutputStream dump, String idName, long id) {
          long token = dump.start(idName, id);
  
          if (mHandler != null) {
              mHandler.dump(dump, "handler", UsbDeviceManagerProto.HANDLER);
          }
  
          dump.end(token);
      }
  
      private native String[] nativeGetAccessoryStrings();
  
      private native ParcelFileDescriptor nativeOpenAccessory();
  
      private native FileDescriptor nativeOpenControl(String usbFunction);
  
      private native boolean nativeIsStartRequested();
  
      private native int nativeGetAudioMode();
  }
  