content: /*
   * Copyright (C) 2016 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.server.usb;
  
  import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.app.ActivityManager;
  import android.content.ActivityNotFoundException;
  import android.content.ComponentName;
  import android.content.Context;
  import android.content.Intent;
  import android.content.pm.ActivityInfo;
  import android.content.pm.ApplicationInfo;
  import android.content.pm.PackageInfo;
  import android.content.pm.PackageManager;
  import android.content.pm.PackageManager.NameNotFoundException;
  import android.content.pm.ResolveInfo;
  import android.content.pm.UserInfo;
  import android.content.res.XmlResourceParser;
  import android.hardware.usb.AccessoryFilter;
  import android.hardware.usb.DeviceFilter;
  import android.hardware.usb.UsbAccessory;
  import android.hardware.usb.UsbDevice;
  import android.hardware.usb.UsbManager;
  import android.os.AsyncTask;
  import android.os.Environment;
  import android.os.UserHandle;
  import android.os.UserManager;
  import android.service.usb.UsbProfileGroupSettingsManagerProto;
  import android.service.usb.UsbSettingsAccessoryPreferenceProto;
  import android.service.usb.UsbSettingsDevicePreferenceProto;
  import android.service.usb.UserPackageProto;
  import android.util.AtomicFile;
  import android.util.Log;
  import android.util.Slog;
  import android.util.SparseArray;
  import android.util.SparseIntArray;
  import android.util.Xml;
  
  import com.android.internal.annotations.GuardedBy;
  import com.android.internal.annotations.Immutable;
  import com.android.internal.content.PackageMonitor;
  import com.android.internal.util.FastXmlSerializer;
  import com.android.internal.util.XmlUtils;
  import com.android.internal.util.dump.DualDumpOutputStream;
  
  import libcore.io.IoUtils;
  
  import org.xmlpull.v1.XmlPullParser;
  import org.xmlpull.v1.XmlPullParserException;
  
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.FileNotFoundException;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.nio.charset.StandardCharsets;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  
  class UsbProfileGroupSettingsManager {
      private static final String TAG = UsbProfileGroupSettingsManager.class.getSimpleName();
      private static final boolean DEBUG = false;
  
      /** Legacy settings file, before multi-user */
      private static final File sSingleUserSettingsFile = new File(
              "/data/system/usb_device_manager.xml");
  
      /** The parent user (main user of the profile group) */
      private final UserHandle mParentUser;
  
      private final AtomicFile mSettingsFile;
      private final boolean mDisablePermissionDialogs;
  
      private final Context mContext;
  
      private final PackageManager mPackageManager;
  
      private final UserManager mUserManager;
      private final @NonNull UsbSettingsManager mSettingsManager;
  
      /** Maps DeviceFilter to user preferred application package */
      @GuardedBy("mLock")
      private final HashMap<DeviceFilter, UserPackage> mDevicePreferenceMap = new HashMap<>();
  
      /** Maps AccessoryFilter to user preferred application package */
      @GuardedBy("mLock")
      private final HashMap<AccessoryFilter, UserPackage> mAccessoryPreferenceMap = new HashMap<>();
  
      private final Object mLock = new Object();
  
      /**
       * If a async task to persist the mDevicePreferenceMap and mAccessoryPreferenceMap is currently
       * scheduled.
       */
      @GuardedBy("mLock")
      private boolean mIsWriteSettingsScheduled;
  
      /**
       * A package of a user.
       */
      @Immutable
      private static class UserPackage {
          /** User */
          final @NonNull UserHandle user;
  
          /** Package name */
          final @NonNull String packageName;
  
          /**
           * Create a description of a per user package.
           *
           * @param packageName The name of the package
           * @param user The user
           */
          private UserPackage(@NonNull String packageName, @NonNull UserHandle user) {
              this.packageName = packageName;
              this.user = user;
          }
  
          @Override
          public boolean equals(Object obj) {
              if (!(obj instanceof UserPackage)) {
                  return false;
              } else {
                  UserPackage other = (UserPackage)obj;
  
                  return user.equals(other.user) && packageName.equals(other.packageName);
              }
          }
  
          @Override
          public int hashCode() {
              int result = user.hashCode();
              result = 31 * result + packageName.hashCode();
              return result;
          }
  
          @Override
          public String toString() {
              return user.getIdentifier() + "/" + packageName;
          }
  
          public void dump(DualDumpOutputStream dump, String idName, long id) {
              long token = dump.start(idName, id);
  
              dump.write("user_id", UserPackageProto.USER_ID, user.getIdentifier());
              dump.write("package_name", UserPackageProto.PACKAGE_NAME, packageName);
  
              dump.end(token);
          }
      }
  
      private class MyPackageMonitor extends PackageMonitor {
          @Override
          public void onPackageAdded(String packageName, int uid) {
              if (!mUserManager.isSameProfileGroup(mParentUser.getIdentifier(),
                      UserHandle.getUserId(uid))) {
                  return;
              }
  
              handlePackageAdded(new UserPackage(packageName, UserHandle.getUserHandleForUid(uid)));
          }
  
          @Override
          public void onPackageRemoved(String packageName, int uid) {
              if (!mUserManager.isSameProfileGroup(mParentUser.getIdentifier(),
                      UserHandle.getUserId(uid))) {
                  return;
              }
  
              clearDefaults(packageName, UserHandle.getUserHandleForUid(uid));
          }
      }
  
      MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
  
      private final UsbHandlerManager mUsbHandlerManager;
  
      private final MtpNotificationManager mMtpNotificationManager;
  
      /**
       * Create new settings manager for a profile group.
       *
       * @param context The context of the service
       * @param user The parent profile
       * @param settingsManager The settings manager of the service
       * @param usbResolveActivityManager The resovle activity manager of the service
       */
      UsbProfileGroupSettingsManager(@NonNull Context context, @NonNull UserHandle user,
              @NonNull UsbSettingsManager settingsManager,
              @NonNull UsbHandlerManager usbResolveActivityManager) {
          if (DEBUG) Slog.v(TAG, "Creating settings for " + user);
  
          Context parentUserContext;
          try {
              parentUserContext = context.createPackageContextAsUser("android", 0, user);
          } catch (NameNotFoundException e) {
              throw new RuntimeException("Missing android package");
          }
  
          mContext = context;
          mPackageManager = context.getPackageManager();
          mSettingsManager = settingsManager;
          mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
  
          mParentUser = user;
          mSettingsFile = new AtomicFile(new File(
                  Environment.getUserSystemDirectory(user.getIdentifier()),
                  "usb_device_manager.xml"), "usb-state");
  
          mDisablePermissionDialogs = context.getResources().getBoolean(
                  com.android.internal.R.bool.config_disableUsbPermissionDialogs);
  
          synchronized (mLock) {
              if (UserHandle.SYSTEM.equals(user)) {
                  upgradeSingleUserLocked();
              }
              readSettingsLocked();
          }
  
          mPackageMonitor.register(context, null, UserHandle.ALL, true);
          mMtpNotificationManager = new MtpNotificationManager(
                  parentUserContext,
                  device -> resolveActivity(createDeviceAttachedIntent(device),
                          device, false /* showMtpNotification */));
  
          mUsbHandlerManager = usbResolveActivityManager;
      }
  
      /**
       * Remove all defaults for a user.
       *
       * @param userToRemove The user the defaults belong to.
       */
      void removeAllDefaultsForUser(@NonNull UserHandle userToRemove) {
          synchronized (mLock) {
              boolean needToPersist = false;
              Iterator<Map.Entry<DeviceFilter, UserPackage>> devicePreferenceIt = mDevicePreferenceMap
                      .entrySet().iterator();
              while (devicePreferenceIt.hasNext()) {
                  Map.Entry<DeviceFilter, UserPackage> entry = devicePreferenceIt.next();
  
                  if (entry.getValue().user.equals(userToRemove)) {
                      devicePreferenceIt.remove();
                      needToPersist = true;
                  }
              }
  
              Iterator<Map.Entry<AccessoryFilter, UserPackage>> accessoryPreferenceIt =
                      mAccessoryPreferenceMap.entrySet().iterator();
              while (accessoryPreferenceIt.hasNext()) {
                  Map.Entry<AccessoryFilter, UserPackage> entry = accessoryPreferenceIt.next();
  
                  if (entry.getValue().user.equals(userToRemove)) {
                      accessoryPreferenceIt.remove();
                      needToPersist = true;
                  }
              }
  
              if (needToPersist) {
                  scheduleWriteSettingsLocked();
              }
          }
      }
  
      private void readPreference(XmlPullParser parser)
              throws XmlPullParserException, IOException {
          String packageName = null;
  
          // If not set, assume it to be the parent profile
          UserHandle user = mParentUser;
  
          int count = parser.getAttributeCount();
          for (int i = 0; i < count; i++) {
              if ("package".equals(parser.getAttributeName(i))) {
                  packageName = parser.getAttributeValue(i);
              }
              if ("user".equals(parser.getAttributeName(i))) {
                  // Might return null if user is not known anymore
                  user = mUserManager
                          .getUserForSerialNumber(Integer.parseInt(parser.getAttributeValue(i)));
              }
          }
  
          XmlUtils.nextElement(parser);
          if ("usb-device".equals(parser.getName())) {
              DeviceFilter filter = DeviceFilter.read(parser);
              if (user != null) {
                  mDevicePreferenceMap.put(filter, new UserPackage(packageName, user));
              }
          } else if ("usb-accessory".equals(parser.getName())) {
              AccessoryFilter filter = AccessoryFilter.read(parser);
              if (user != null) {
                  mAccessoryPreferenceMap.put(filter, new UserPackage(packageName, user));
              }
          }
          XmlUtils.nextElement(parser);
      }
  
      /**
       * Upgrade any single-user settings from {@link #sSingleUserSettingsFile}.
       * Should only by called by owner.
       */
      @GuardedBy("mLock")
      private void upgradeSingleUserLocked() {
          if (sSingleUserSettingsFile.exists()) {
              mDevicePreferenceMap.clear();
              mAccessoryPreferenceMap.clear();
  
              FileInputStream fis = null;
              try {
                  fis = new FileInputStream(sSingleUserSettingsFile);
                  XmlPullParser parser = Xml.newPullParser();
                  parser.setInput(fis, StandardCharsets.UTF_8.name());
  
                  XmlUtils.nextElement(parser);
                  while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
                      final String tagName = parser.getName();
                      if ("preference".equals(tagName)) {
                          readPreference(parser);
                      } else {
                          XmlUtils.nextElement(parser);
                      }
                  }
              } catch (IOException | XmlPullParserException e) {
                  Log.wtf(TAG, "Failed to read single-user settings", e);
              } finally {
                  IoUtils.closeQuietly(fis);
              }
  
              scheduleWriteSettingsLocked();
  
              // Success or failure, we delete single-user file
              sSingleUserSettingsFile.delete();
          }
      }
  
      @GuardedBy("mLock")
      private void readSettingsLocked() {
          if (DEBUG) Slog.v(TAG, "readSettingsLocked()");
  
          mDevicePreferenceMap.clear();
          mAccessoryPreferenceMap.clear();
  
          FileInputStream stream = null;
          try {
              stream = mSettingsFile.openRead();
              XmlPullParser parser = Xml.newPullParser();
              parser.setInput(stream, StandardCharsets.UTF_8.name());
  
              XmlUtils.nextElement(parser);
              while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
                  String tagName = parser.getName();
                  if ("preference".equals(tagName)) {
                      readPreference(parser);
                  } else {
                      XmlUtils.nextElement(parser);
                  }
              }
          } catch (FileNotFoundException e) {
              if (DEBUG) Slog.d(TAG, "settings file not found");
          } catch (Exception e) {
              Slog.e(TAG, "error reading settings file, deleting to start fresh", e);
              mSettingsFile.delete();
          } finally {
              IoUtils.closeQuietly(stream);
          }
      }
  
      /**
       * Schedule a async task to persist {@link #mDevicePreferenceMap} and
       * {@link #mAccessoryPreferenceMap}. If a task is already scheduled but not completed, do
       * nothing as the currently scheduled one will do the work.
       * <p>Called with {@link #mLock} held.</p>
       * <p>In the uncommon case that the system crashes in between the scheduling and the write the
       * update is lost.</p>
       */
      @GuardedBy("mLock")
      private void scheduleWriteSettingsLocked() {
          if (mIsWriteSettingsScheduled) {
              return;
          } else {
              mIsWriteSettingsScheduled = true;
          }
  
          AsyncTask.execute(() -> {
              synchronized (mLock) {
                  FileOutputStream fos = null;
                  try {
                      fos = mSettingsFile.startWrite();
  
                      FastXmlSerializer serializer = new FastXmlSerializer();
                      serializer.setOutput(fos, StandardCharsets.UTF_8.name());
                      serializer.startDocument(null, true);
                      serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output",
                                      true);
                      serializer.startTag(null, "settings");
  
                      for (DeviceFilter filter : mDevicePreferenceMap.keySet()) {
                          serializer.startTag(null, "preference");
                          serializer.attribute(null, "package",
                                  mDevicePreferenceMap.get(filter).packageName);
                          serializer.attribute(null, "user",
                                  String.valueOf(getSerial(mDevicePreferenceMap.get(filter).user)));
                          filter.write(serializer);
                          serializer.endTag(null, "preference");
                      }
  
                      for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) {
                          serializer.startTag(null, "preference");
                          serializer.attribute(null, "package",
                                  mAccessoryPreferenceMap.get(filter).packageName);
                          serializer.attribute(null, "user", String.valueOf(
                                          getSerial(mAccessoryPreferenceMap.get(filter).user)));
                          filter.write(serializer);
                          serializer.endTag(null, "preference");
                      }
  
                      serializer.endTag(null, "settings");
                      serializer.endDocument();
  
                      mSettingsFile.finishWrite(fos);
                  } catch (IOException e) {
                      Slog.e(TAG, "Failed to write settings", e);
                      if (fos != null) {
                          mSettingsFile.failWrite(fos);
                      }
                  }
  
                  mIsWriteSettingsScheduled = false;
              }
          });
      }
  
      /**
       * Get {@link DeviceFilter} for all devices an activity should be launched for.
       *
       * @param pm The package manager used to get the device filter files
       * @param info The {@link ResolveInfo} for the activity that can handle usb device attached
       *             events
       *
       * @return The list of {@link DeviceFilter} the activity should be called for or {@code null} if
       *         none
       */
      @Nullable
      static ArrayList<DeviceFilter> getDeviceFilters(@NonNull PackageManager pm,
              @NonNull ResolveInfo info) {
          ArrayList<DeviceFilter> filters = null;
          ActivityInfo ai = info.activityInfo;
  
          XmlResourceParser parser = null;
          try {
              parser = ai.loadXmlMetaData(pm, UsbManager.ACTION_USB_DEVICE_ATTACHED);
              if (parser == null) {
                  Slog.w(TAG, "no meta-data for " + info);
                  return null;
              }
  
              XmlUtils.nextElement(parser);
              while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
                  String tagName = parser.getName();
                  if ("usb-device".equals(tagName)) {
                      if (filters == null) {
                          filters = new ArrayList<>(1);
                      }
                      filters.add(DeviceFilter.read(parser));
                  }
                  XmlUtils.nextElement(parser);
              }
          } catch (Exception e) {
              Slog.w(TAG, "Unable to load component info " + info.toString(), e);
          } finally {
              if (parser != null) parser.close();
          }
          return filters;
      }
  
      /**
       * Get {@link AccessoryFilter} for all accessories an activity should be launched for.
       *
       * @param pm The package manager used to get the accessory filter files
       * @param info The {@link ResolveInfo} for the activity that can handle usb accessory attached
       *             events
       *
       * @return The list of {@link AccessoryFilter} the activity should be called for or {@code null}
       *         if none
       */
      static @Nullable ArrayList<AccessoryFilter> getAccessoryFilters(@NonNull PackageManager pm,
              @NonNull ResolveInfo info) {
          ArrayList<AccessoryFilter> filters = null;
          ActivityInfo ai = info.activityInfo;
  
          XmlResourceParser parser = null;
          try {
              parser = ai.loadXmlMetaData(pm, UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
              if (parser == null) {
                  Slog.w(TAG, "no meta-data for " + info);
                  return null;
              }
  
              XmlUtils.nextElement(parser);
              while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
                  String tagName = parser.getName();
                  if ("usb-accessory".equals(tagName)) {
                      if (filters == null) {
                          filters = new ArrayList<>(1);
                      }
                      filters.add(AccessoryFilter.read(parser));
                  }
                  XmlUtils.nextElement(parser);
              }
          } catch (Exception e) {
              Slog.w(TAG, "Unable to load component info " + info.toString(), e);
          } finally {
              if (parser != null) parser.close();
          }
          return filters;
      }
  
      // Checks to see if a package matches a device or accessory.
      // Only one of device and accessory should be non-null.
      private boolean packageMatchesLocked(ResolveInfo info, UsbDevice device,
              UsbAccessory accessory) {
          if (isForwardMatch(info)) {
              return true;
          }
  
          if (device != null) {
              ArrayList<DeviceFilter> deviceFilters = getDeviceFilters(mPackageManager, info);
              if (deviceFilters != null) {
                  int numDeviceFilters = deviceFilters.size();
                  for (int i = 0; i < numDeviceFilters; i++) {
                      if (deviceFilters.get(i).matches(device)) {
                          return true;
                      }
                  }
              }
          }
  
          if (accessory != null) {
              ArrayList<AccessoryFilter> accessoryFilters = getAccessoryFilters(mPackageManager,
                      info);
              if (accessoryFilters != null) {
                  int numAccessoryFilters = accessoryFilters.size();
                  for (int i = 0; i < numAccessoryFilters; i++) {
                      if (accessoryFilters.get(i).matches(accessory)) {
                          return true;
                      }
                  }
              }
          }
  
          return false;
      }
  
      /**
       * Resolve all activities that match an intent for all profiles of this group.
       *
       * @param intent The intent to resolve
       *
       * @return The {@link ResolveInfo}s for all profiles of the group
       */
      private @NonNull ArrayList<ResolveInfo> queryIntentActivitiesForAllProfiles(
              @NonNull Intent intent) {
          List<UserInfo> profiles = mUserManager.getEnabledProfiles(mParentUser.getIdentifier());
  
          ArrayList<ResolveInfo> resolveInfos = new ArrayList<>();
          int numProfiles = profiles.size();
          for (int i = 0; i < numProfiles; i++) {
              resolveInfos.addAll(mSettingsManager.getSettingsForUser(profiles.get(i).id)
                      .queryIntentActivities(intent));
          }
  
          return resolveInfos;
      }
  
      /**
       * If this match used to forward the intent to another profile?
       *
       * @param match The match
       *
       * @return {@code true} iff this is such a forward match
       */
      private boolean isForwardMatch(@NonNull ResolveInfo match) {
          return match.getComponentInfo().name.equals(FORWARD_INTENT_TO_MANAGED_PROFILE);
      }
  
      /**
       * Only return those matches with the highest priority.
       *
       * @param matches All matches, some might have lower priority
       *
       * @return The matches with the highest priority
       */
      @NonNull
      private ArrayList<ResolveInfo> preferHighPriority(@NonNull ArrayList<ResolveInfo> matches) {
          SparseArray<ArrayList<ResolveInfo>> highestPriorityMatchesByUserId = new SparseArray<>();
          SparseIntArray highestPriorityByUserId = new SparseIntArray();
          ArrayList<ResolveInfo> forwardMatches = new ArrayList<>();
  
          // Create list of highest priority matches per user in highestPriorityMatchesByUserId
          int numMatches = matches.size();
          for (int matchNum = 0; matchNum < numMatches; matchNum++) {
              ResolveInfo match = matches.get(matchNum);
  
              // Unnecessary forward matches are filtered out later, hence collect them all to add
              // them below
              if (isForwardMatch(match)) {
                  forwardMatches.add(match);
                  continue;
              }
  
              // If this a previously unknown user?
              if (highestPriorityByUserId.indexOfKey(match.targetUserId) < 0) {
                  highestPriorityByUserId.put(match.targetUserId, Integer.MIN_VALUE);
                  highestPriorityMatchesByUserId.put(match.targetUserId, new ArrayList<>());
              }
  
              // Find current highest priority matches for the current user
              int highestPriority = highestPriorityByUserId.get(match.targetUserId);
              ArrayList<ResolveInfo> highestPriorityMatches = highestPriorityMatchesByUserId.get(
                      match.targetUserId);
  
              if (match.priority == highestPriority) {
                  highestPriorityMatches.add(match);
              } else if (match.priority > highestPriority) {
                  highestPriorityByUserId.put(match.targetUserId, match.priority);
  
                  highestPriorityMatches.clear();
                  highestPriorityMatches.add(match);
              }
          }
  
          // Combine all users (+ forward matches) back together. This means that all non-forward
          // matches have the same priority for a user. Matches for different users might have
          // different priority.
          ArrayList<ResolveInfo> combinedMatches = new ArrayList<>(forwardMatches);
          int numMatchArrays = highestPriorityMatchesByUserId.size();
          for (int matchArrayNum = 0; matchArrayNum < numMatchArrays; matchArrayNum++) {
              combinedMatches.addAll(highestPriorityMatchesByUserId.valueAt(matchArrayNum));
          }
  
          return combinedMatches;
      }
  
      /**
       * If there are no matches for a profile, remove the forward intent to this profile.
       *
       * @param rawMatches The matches that contain all forward intents
       *
       * @return The matches with the unnecessary forward intents removed
       */
      @NonNull private ArrayList<ResolveInfo> removeForwardIntentIfNotNeeded(
              @NonNull ArrayList<ResolveInfo> rawMatches) {
          final int numRawMatches = rawMatches.size();
  
          // The raw matches contain the activities that can be started but also the intents to
          // forward the intent to the other profile
          int numParentActivityMatches = 0;
          int numNonParentActivityMatches = 0;
          for (int i = 0; i < numRawMatches; i++) {
              final ResolveInfo rawMatch = rawMatches.get(i);
              if (!isForwardMatch(rawMatch)) {
                  if (UserHandle.getUserHandleForUid(
                          rawMatch.activityInfo.applicationInfo.uid).equals(mParentUser)) {
                      numParentActivityMatches++;
                  } else {
                      numNonParentActivityMatches++;
                  }
              }
          }
  
          // If only one profile has activity matches, we need to remove all switch intents
          if (numParentActivityMatches == 0 || numNonParentActivityMatches == 0) {
              ArrayList<ResolveInfo> matches = new ArrayList<>(
                      numParentActivityMatches + numNonParentActivityMatches);
  
              for (int i = 0; i < numRawMatches; i++) {
                  ResolveInfo rawMatch = rawMatches.get(i);
                  if (!isForwardMatch(rawMatch)) {
                      matches.add(rawMatch);
                  }
              }
              return matches;
  
          } else {
              return rawMatches;
          }
      }
  
      private ArrayList<ResolveInfo> getDeviceMatchesLocked(UsbDevice device, Intent intent) {
          ArrayList<ResolveInfo> matches = new ArrayList<>();
          List<ResolveInfo> resolveInfos = queryIntentActivitiesForAllProfiles(intent);
          int count = resolveInfos.size();
          for (int i = 0; i < count; i++) {
              ResolveInfo resolveInfo = resolveInfos.get(i);
              if (packageMatchesLocked(resolveInfo, device, null)) {
                  matches.add(resolveInfo);
              }
          }
  
          return removeForwardIntentIfNotNeeded(preferHighPriority(matches));
      }
  
      private ArrayList<ResolveInfo> getAccessoryMatchesLocked(
              UsbAccessory accessory, Intent intent) {
          ArrayList<ResolveInfo> matches = new ArrayList<>();
          List<ResolveInfo> resolveInfos = queryIntentActivitiesForAllProfiles(intent);
          int count = resolveInfos.size();
          for (int i = 0; i < count; i++) {
              ResolveInfo resolveInfo = resolveInfos.get(i);
              if (packageMatchesLocked(resolveInfo, null, accessory)) {
                  matches.add(resolveInfo);
              }
          }
  
          return removeForwardIntentIfNotNeeded(preferHighPriority(matches));
      }
  
      public void deviceAttached(UsbDevice device) {
          final Intent intent = createDeviceAttachedIntent(device);
  
          // Send broadcast to running activities with registered intent
          mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
  
          resolveActivity(intent, device, true /* showMtpNotification */);
      }
  
      private void resolveActivity(Intent intent, UsbDevice device, boolean showMtpNotification) {
          final ArrayList<ResolveInfo> matches;
          final ActivityInfo defaultActivity;
          synchronized (mLock) {
              matches = getDeviceMatchesLocked(device, intent);
              defaultActivity = getDefaultActivityLocked(
                      matches, mDevicePreferenceMap.get(new DeviceFilter(device)));
          }
  
          if (showMtpNotification && MtpNotificationManager.shouldShowNotification(
                  mPackageManager, device) && defaultActivity == null) {
              // Show notification if the device is MTP storage.
              mMtpNotificationManager.showNotification(device);
              return;
          }
  
          // Start activity with registered intent
          resolveActivity(intent, matches, defaultActivity, device, null);
      }
  
      public void deviceAttachedForFixedHandler(UsbDevice device, ComponentName component) {
          final Intent intent = createDeviceAttachedIntent(device);
  
          // Send broadcast to running activity with registered intent
          mContext.sendBroadcastAsUser(intent, UserHandle.of(ActivityManager.getCurrentUser()));
  
          ApplicationInfo appInfo;
          try {
              // Fixed handlers are always for parent user
              appInfo = mPackageManager.getApplicationInfoAsUser(component.getPackageName(), 0,
                      mParentUser.getIdentifier());
          } catch (NameNotFoundException e) {
              Slog.e(TAG, "Default USB handling package (" + component.getPackageName()
                      + ") not found  for user " + mParentUser);
              return;
          }
  
          mSettingsManager.getSettingsForUser(UserHandle.getUserId(appInfo.uid))
                  .grantDevicePermission(device, appInfo.uid);
  
          Intent activityIntent = new Intent(intent);
          activityIntent.setComponent(component);
          try {
              mContext.startActivityAsUser(activityIntent, mParentUser);
          } catch (ActivityNotFoundException e) {
              Slog.e(TAG, "unable to start activity " + activityIntent);
          }
      }
  
      /**
       * Remove notifications for a usb device.
       *
       * @param device The device the notifications are for.
       */
      void usbDeviceRemoved(@NonNull UsbDevice device) {
          mMtpNotificationManager.hideNotification(device.getDeviceId());
      }
  
      public void accessoryAttached(UsbAccessory accessory) {
          Intent intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
          intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
          intent.addFlags(
                  Intent.FLAG_ACTIVITY_NEW_TASK |
                  Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
  
          final ArrayList<ResolveInfo> matches;
          final ActivityInfo defaultActivity;
          synchronized (mLock) {
              matches = getAccessoryMatchesLocked(accessory, intent);
              defaultActivity = getDefaultActivityLocked(
                      matches, mAccessoryPreferenceMap.get(new AccessoryFilter(accessory)));
          }
  
          resolveActivity(intent, matches, defaultActivity, null, accessory);
      }
  
      /**
       * Start the appropriate package when an device/accessory got attached.
       *
       * @param intent The intent to start the package
       * @param matches The available resolutions of the intent
       * @param defaultActivity The default activity for the device (if set)
       * @param device The device if a device was attached
       * @param accessory The accessory if a device was attached
       */
      private void resolveActivity(@NonNull Intent intent, @NonNull ArrayList<ResolveInfo> matches,
              @Nullable ActivityInfo defaultActivity, @Nullable UsbDevice device,
              @Nullable UsbAccessory accessory) {
          // don't show the resolver activity if there are no choices available
          if (matches.size() == 0) {
              if (accessory != null) {
                  mUsbHandlerManager.showUsbAccessoryUriActivity(accessory, mParentUser);
              }
              // do nothing
              return;
          }
  
          if (defaultActivity != null) {
              UsbUserSettingsManager defaultRIUserSettings = mSettingsManager.getSettingsForUser(
                      UserHandle.getUserId(defaultActivity.applicationInfo.uid));
              // grant permission for default activity
              if (device != null) {
                  defaultRIUserSettings.
                          grantDevicePermission(device, defaultActivity.applicationInfo.uid);
              } else if (accessory != null) {
                  defaultRIUserSettings.grantAccessoryPermission(accessory,
                          defaultActivity.applicationInfo.uid);
              }
  
              // start default activity directly
              try {
                  intent.setComponent(
                          new ComponentName(defaultActivity.packageName, defaultActivity.name));
  
                  UserHandle user = UserHandle.getUserHandleForUid(
                          defaultActivity.applicationInfo.uid);
                  mContext.startActivityAsUser(intent, user);
              } catch (ActivityNotFoundException e) {
                  Slog.e(TAG, "startActivity failed", e);
              }
          } else {
              if (matches.size() == 1) {
                  mUsbHandlerManager.confirmUsbHandler(matches.get(0), device, accessory);
              } else {
                  mUsbHandlerManager.selectUsbHandler(matches, mParentUser, intent);
              }
          }
      }
  
      /**
       * Returns a default activity for matched ResolveInfo.
       * @param matches Resolved activities matched with connected device/accesary.
       * @param userPackage Default activity choosed by a user before. Should be null if no activity
       *     is choosed by a user.
       * @return Default activity
       */
      private @Nullable ActivityInfo getDefaultActivityLocked(
              @NonNull ArrayList<ResolveInfo> matches,
              @Nullable UserPackage userPackage) {
          if (userPackage != null) {
              // look for default activity
              for (final ResolveInfo info : matches) {
                  if (info.activityInfo != null && userPackage.equals(
                          new UserPackage(info.activityInfo.packageName,
                                  UserHandle.getUserHandleForUid(
                                          info.activityInfo.applicationInfo.uid)))) {
                      return info.activityInfo;
                  }
              }
          }
  
          if (matches.size() == 1) {
              final ActivityInfo activityInfo = matches.get(0).activityInfo;
              if (activityInfo != null) {
                  if (mDisablePermissionDialogs) {
                      return activityInfo;
                  }
                  // System apps are considered default unless there are other matches
                  if (activityInfo.applicationInfo != null
                          && (activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
                                  != 0) {
                      return activityInfo;
                  }
              }
          }
  
          return null;
      }
  
      @GuardedBy("mLock")
      private boolean clearCompatibleMatchesLocked(@NonNull UserPackage userPackage,
              @NonNull DeviceFilter filter) {
          ArrayList<DeviceFilter> keysToRemove = new ArrayList<>();
  
          // The keys in mDevicePreferenceMap are filters that match devices very narrowly
          for (DeviceFilter device : mDevicePreferenceMap.keySet()) {
              if (filter.contains(device)) {
                  UserPackage currentMatch = mDevicePreferenceMap.get(device);
                  if (!currentMatch.equals(userPackage)) {
                      keysToRemove.add(device);
                  }
              }
          }
  
          if (!keysToRemove.isEmpty()) {
              for (DeviceFilter keyToRemove : keysToRemove) {
                  mDevicePreferenceMap.remove(keyToRemove);
              }
          }
  
          return !keysToRemove.isEmpty();
      }
  
      @GuardedBy("mLock")
      private boolean clearCompatibleMatchesLocked(@NonNull UserPackage userPackage,
              @NonNull AccessoryFilter filter) {
          ArrayList<AccessoryFilter> keysToRemove = new ArrayList<>();
  
          // The keys in mAccessoryPreferenceMap are filters that match accessories very narrowly
          for (AccessoryFilter accessory : mAccessoryPreferenceMap.keySet()) {
              if (filter.contains(accessory)) {
                  UserPackage currentMatch = mAccessoryPreferenceMap.get(accessory);
                  if (!currentMatch.equals(userPackage)) {
                      keysToRemove.add(accessory);
                  }
              }
          }
  
          if (!keysToRemove.isEmpty()) {
              for (AccessoryFilter keyToRemove : keysToRemove) {
                  mAccessoryPreferenceMap.remove(keyToRemove);
              }
          }
  
          return !keysToRemove.isEmpty();
      }
  
      @GuardedBy("mLock")
      private boolean handlePackageAddedLocked(UserPackage userPackage, ActivityInfo aInfo,
              String metaDataName) {
          XmlResourceParser parser = null;
          boolean changed = false;
  
          try {
              parser = aInfo.loadXmlMetaData(mPackageManager, metaDataName);
              if (parser == null) return false;
  
              XmlUtils.nextElement(parser);
              while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
                  String tagName = parser.getName();
                  if ("usb-device".equals(tagName)) {
                      DeviceFilter filter = DeviceFilter.read(parser);
                      if (clearCompatibleMatchesLocked(userPackage, filter)) {
                          changed = true;
                      }
                  }
                  else if ("usb-accessory".equals(tagName)) {
                      AccessoryFilter filter = AccessoryFilter.read(parser);
                      if (clearCompatibleMatchesLocked(userPackage, filter)) {
                          changed = true;
                      }
                  }
                  XmlUtils.nextElement(parser);
              }
          } catch (Exception e) {
              Slog.w(TAG, "Unable to load component info " + aInfo.toString(), e);
          } finally {
              if (parser != null) parser.close();
          }
          return changed;
      }
  
      // Check to see if the package supports any USB devices or accessories.
      // If so, clear any preferences for matching devices/accessories.
      private void handlePackageAdded(@NonNull UserPackage userPackage) {
          synchronized (mLock) {
              PackageInfo info;
              boolean changed = false;
  
              try {
                  info = mPackageManager.getPackageInfoAsUser(userPackage.packageName,
                          PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA,
                          userPackage.user.getIdentifier());
              } catch (NameNotFoundException e) {
                  Slog.e(TAG, "handlePackageUpdate could not find package " + userPackage, e);
                  return;
              }
  
              ActivityInfo[] activities = info.activities;
              if (activities == null) return;
              for (int i = 0; i < activities.length; i++) {
                  // check for meta-data, both for devices and accessories
                  if (handlePackageAddedLocked(userPackage, activities[i],
                          UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
                      changed = true;
                  }
  
                  if (handlePackageAddedLocked(userPackage, activities[i],
                          UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) {
                      changed = true;
                  }
              }
  
              if (changed) {
                  scheduleWriteSettingsLocked();
              }
          }
      }
  
      /**
       * Get the serial number for a user handle.
       *
       * @param user The user handle
       *
       * @return The serial number
       */
      private int getSerial(@NonNull UserHandle user) {
          return mUserManager.getUserSerialNumber(user.getIdentifier());
      }
  
      /**
       * Set a package as default handler for a device.
       *
       * @param device The device that should be handled by default
       * @param packageName The default handler package
       * @param user The user the package belongs to
       */
      void setDevicePackage(@NonNull UsbDevice device, @Nullable String packageName,
              @NonNull UserHandle user) {
          DeviceFilter filter = new DeviceFilter(device);
          boolean changed;
          synchronized (mLock) {
              if (packageName == null) {
                  changed = (mDevicePreferenceMap.remove(filter) != null);
              } else {
                  UserPackage userPackage = new UserPackage(packageName, user);
  
                  changed = !userPackage.equals(mDevicePreferenceMap.get(filter));
                  if (changed) {
                      mDevicePreferenceMap.put(filter, userPackage);
                  }
              }
              if (changed) {
                  scheduleWriteSettingsLocked();
              }
          }
      }
  
      /**
       * Set a package as default handler for a accessory.
       *
       * @param accessory The accessory that should be handled by default
       * @param packageName The default handler package
       * @param user The user the package belongs to
       */
      void setAccessoryPackage(@NonNull UsbAccessory accessory, @Nullable String packageName,
              @NonNull UserHandle user) {
          AccessoryFilter filter = new AccessoryFilter(accessory);
          boolean changed;
          synchronized (mLock) {
              if (packageName == null) {
                  changed = (mAccessoryPreferenceMap.remove(filter) != null);
              } else {
                  UserPackage userPackage = new UserPackage(packageName, user);
  
                  changed = !userPackage.equals(mAccessoryPreferenceMap.get(filter));
                  if (changed) {
                      mAccessoryPreferenceMap.put(filter, userPackage);
                  }
              }
              if (changed) {
                  scheduleWriteSettingsLocked();
              }
          }
      }
  
      /**
       * Check if a package has is the default handler for any usb device or accessory.
       *
       * @param packageName The package name
       * @param user The user the package belongs to
       *
       * @return {@code true} iff the package is default for any usb device or accessory
       */
      boolean hasDefaults(@NonNull String packageName, @NonNull UserHandle user) {
          UserPackage userPackage = new UserPackage(packageName, user);
          synchronized (mLock) {
              if (mDevicePreferenceMap.values().contains(userPackage)) return true;
              return mAccessoryPreferenceMap.values().contains(userPackage);
          }
      }
  
      /**
       * Clear defaults for a package from any preference.
       *
       * @param packageName The package to remove
       * @param user The user the package belongs to
       */
      void clearDefaults(@NonNull String packageName, @NonNull UserHandle user) {
          UserPackage userPackage = new UserPackage(packageName, user);
  
          synchronized (mLock) {
              if (clearPackageDefaultsLocked(userPackage)) {
                  scheduleWriteSettingsLocked();
              }
          }
      }
  
      /**
       * Clear defaults for a package from any preference (does not persist).
       *
       * @param userPackage The package to remove
       *
       * @return {@code true} iff at least one preference was cleared
       */
      private boolean clearPackageDefaultsLocked(@NonNull UserPackage userPackage) {
          boolean cleared = false;
          synchronized (mLock) {
              if (mDevicePreferenceMap.containsValue(userPackage)) {
                  // make a copy of the key set to avoid ConcurrentModificationException
                  DeviceFilter[] keys = mDevicePreferenceMap.keySet().toArray(new DeviceFilter[0]);
                  for (int i = 0; i < keys.length; i++) {
                      DeviceFilter key = keys[i];
                      if (userPackage.equals(mDevicePreferenceMap.get(key))) {
                          mDevicePreferenceMap.remove(key);
                          cleared = true;
                      }
                  }
              }
              if (mAccessoryPreferenceMap.containsValue(userPackage)) {
                  // make a copy of the key set to avoid ConcurrentModificationException
                  AccessoryFilter[] keys =
                          mAccessoryPreferenceMap.keySet().toArray(new AccessoryFilter[0]);
                  for (int i = 0; i < keys.length; i++) {
                      AccessoryFilter key = keys[i];
                      if (userPackage.equals(mAccessoryPreferenceMap.get(key))) {
                          mAccessoryPreferenceMap.remove(key);
                          cleared = true;
                      }
                  }
              }
              return cleared;
          }
      }
  
      public void dump(@NonNull DualDumpOutputStream dump, @NonNull String idName, long id) {
          long token = dump.start(idName, id);
  
          synchronized (mLock) {
              dump.write("parent_user_id", UsbProfileGroupSettingsManagerProto.PARENT_USER_ID,
                      mParentUser.getIdentifier());
  
              for (DeviceFilter filter : mDevicePreferenceMap.keySet()) {
                  long devicePrefToken = dump.start("device_preferences",
                          UsbProfileGroupSettingsManagerProto.DEVICE_PREFERENCES);
  
                  filter.dump(dump, "filter", UsbSettingsDevicePreferenceProto.FILTER);
  
                  mDevicePreferenceMap.get(filter).dump(dump, "user_package",
                          UsbSettingsDevicePreferenceProto.USER_PACKAGE);
  
                  dump.end(devicePrefToken);
              }
              for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) {
                  long accessoryPrefToken = dump.start("accessory_preferences",
                          UsbProfileGroupSettingsManagerProto.ACCESSORY_PREFERENCES);
  
                  filter.dump(dump, "filter", UsbSettingsAccessoryPreferenceProto.FILTER);
  
                  mAccessoryPreferenceMap.get(filter).dump(dump, "user_package",
                          UsbSettingsAccessoryPreferenceProto.USER_PACKAGE);
  
                  dump.end(accessoryPrefToken);
              }
          }
  
          dump.end(token);
      }
  
      private static Intent createDeviceAttachedIntent(UsbDevice device) {
          Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
          intent.putExtra(UsbManager.EXTRA_DEVICE, device);
          intent.addFlags(
                  Intent.FLAG_ACTIVITY_NEW_TASK |
                  Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
          return intent;
      }
  }
  