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 and
   * limitations under the License.
   */
  
  package com.android.server.appwidget;
  
  import static android.content.Context.KEYGUARD_SERVICE;
  import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
  import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
  import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
  
  import android.annotation.UserIdInt;
  import android.app.ActivityManager;
  import android.app.AlarmManager;
  import android.app.AppGlobals;
  import android.app.AppOpsManager;
  import android.app.IApplicationThread;
  import android.app.IServiceConnection;
  import android.app.KeyguardManager;
  import android.app.PendingIntent;
  import android.app.admin.DevicePolicyManagerInternal;
  import android.app.admin.DevicePolicyManagerInternal.OnCrossProfileWidgetProvidersChangeListener;
  import android.appwidget.AppWidgetManager;
  import android.appwidget.AppWidgetManagerInternal;
  import android.appwidget.AppWidgetProviderInfo;
  import android.appwidget.PendingHostUpdate;
  import android.content.BroadcastReceiver;
  import android.content.ComponentName;
  import android.content.Context;
  import android.content.Intent;
  import android.content.Intent.FilterComparison;
  import android.content.IntentFilter;
  import android.content.IntentSender;
  import android.content.ServiceConnection;
  import android.content.pm.ActivityInfo;
  import android.content.pm.ApplicationInfo;
  import android.content.pm.IPackageManager;
  import android.content.pm.LauncherApps;
  import android.content.pm.PackageInfo;
  import android.content.pm.PackageManager;
  import android.content.pm.PackageManager.NameNotFoundException;
  import android.content.pm.PackageManagerInternal;
  import android.content.pm.ParceledListSlice;
  import android.content.pm.ResolveInfo;
  import android.content.pm.ServiceInfo;
  import android.content.pm.ShortcutServiceInternal;
  import android.content.pm.SuspendDialogInfo;
  import android.content.pm.UserInfo;
  import android.content.res.Resources;
  import android.content.res.TypedArray;
  import android.content.res.XmlResourceParser;
  import android.graphics.Bitmap;
  import android.graphics.Point;
  import android.graphics.drawable.Drawable;
  import android.net.Uri;
  import android.os.Binder;
  import android.os.Bundle;
  import android.os.Environment;
  import android.os.Handler;
  import android.os.IBinder;
  import android.os.Looper;
  import android.os.Message;
  import android.os.Process;
  import android.os.RemoteException;
  import android.os.SystemClock;
  import android.os.Trace;
  import android.os.UserHandle;
  import android.os.UserManager;
  import android.service.appwidget.AppWidgetServiceDumpProto;
  import android.service.appwidget.WidgetProto;
  import android.text.TextUtils;
  import android.util.ArraySet;
  import android.util.AtomicFile;
  import android.util.AttributeSet;
  import android.util.LongSparseArray;
  import android.util.Pair;
  import android.util.Slog;
  import android.util.SparseArray;
  import android.util.SparseIntArray;
  import android.util.SparseLongArray;
  import android.util.TypedValue;
  import android.util.Xml;
  import android.util.proto.ProtoOutputStream;
  import android.view.Display;
  import android.view.View;
  import android.view.WindowManager;
  import android.widget.RemoteViews;
  
  import com.android.internal.R;
  import com.android.internal.app.SuspendedAppActivity;
  import com.android.internal.app.UnlaunchableAppActivity;
  import com.android.internal.appwidget.IAppWidgetHost;
  import com.android.internal.appwidget.IAppWidgetService;
  import com.android.internal.os.BackgroundThread;
  import com.android.internal.os.SomeArgs;
  import com.android.internal.util.ArrayUtils;
  import com.android.internal.util.DumpUtils;
  import com.android.internal.util.FastXmlSerializer;
  import com.android.internal.widget.IRemoteViewsFactory;
  import com.android.server.LocalServices;
  import com.android.server.WidgetBackupProvider;
  import com.android.server.policy.IconUtilities;
  
  import org.xmlpull.v1.XmlPullParser;
  import org.xmlpull.v1.XmlPullParserException;
  import org.xmlpull.v1.XmlSerializer;
  
  import java.io.ByteArrayInputStream;
  import java.io.ByteArrayOutputStream;
  import java.io.File;
  import java.io.FileDescriptor;
  import java.io.FileInputStream;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.PrintWriter;
  import java.nio.charset.StandardCharsets;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  import java.util.Objects;
  import java.util.Set;
  import java.util.concurrent.atomic.AtomicLong;
  
  class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBackupProvider,
          OnCrossProfileWidgetProvidersChangeListener {
      private static final String TAG = "AppWidgetServiceImpl";
  
      private static boolean DEBUG = false;
  
      private static final String OLD_KEYGUARD_HOST_PACKAGE = "android";
      private static final String NEW_KEYGUARD_HOST_PACKAGE = "com.android.keyguard";
      private static final int KEYGUARD_HOST_ID = 0x4b455947;
  
      private static final String STATE_FILENAME = "appwidgets.xml";
  
      private static final int MIN_UPDATE_PERIOD = DEBUG ? 0 : 30 * 60 * 1000; // 30 minutes
  
      private static final int TAG_UNDEFINED = -1;
  
      private static final int UNKNOWN_UID = -1;
  
      private static final int LOADED_PROFILE_ID = -1;
  
      private static final int UNKNOWN_USER_ID = -10;
  
      // Bump if the stored widgets need to be upgraded.
      private static final int CURRENT_VERSION = 1;
  
      // Every widget update request is associated which an increasing sequence number. This is
      // used to verify which request has successfully been received by the host.
      private static final AtomicLong UPDATE_COUNTER = new AtomicLong();
  
      private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
          @Override
          public void onReceive(Context context, Intent intent) {
              final String action = intent.getAction();
              final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
  
              if (DEBUG) {
                  Slog.i(TAG, "Received broadcast: " + action + " on user " + userId);
              }
  
              switch (action) {
                  case Intent.ACTION_CONFIGURATION_CHANGED:
                      onConfigurationChanged();
                      break;
                  case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
                  case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
                      synchronized (mLock) {
                          reloadWidgetsMaskedState(userId);
                      }
                      break;
                  case Intent.ACTION_PACKAGES_SUSPENDED:
                      onPackageBroadcastReceived(intent, getSendingUserId());
                      updateWidgetPackageSuspensionMaskedState(intent, true, getSendingUserId());
                      break;
                  case Intent.ACTION_PACKAGES_UNSUSPENDED:
                      onPackageBroadcastReceived(intent, getSendingUserId());
                      updateWidgetPackageSuspensionMaskedState(intent, false, getSendingUserId());
                      break;
                  default:
                      onPackageBroadcastReceived(intent, getSendingUserId());
                      break;
              }
          }
      };
  
      // Manages persistent references to RemoteViewsServices from different App Widgets.
      private final HashMap<Pair<Integer, FilterComparison>, HashSet<Integer>>
              mRemoteViewsServicesAppWidgets = new HashMap<>();
  
      private final Object mLock = new Object();
  
      private final ArrayList<Widget> mWidgets = new ArrayList<>();
      private final ArrayList<Host> mHosts = new ArrayList<>();
      private final ArrayList<Provider> mProviders = new ArrayList<>();
  
      private final ArraySet<Pair<Integer, String>> mPackagesWithBindWidgetPermission =
              new ArraySet<>();
  
      private final SparseIntArray mLoadedUserIds = new SparseIntArray();
  
      private final SparseArray<ArraySet<String>> mWidgetPackages = new SparseArray<>();
  
      private BackupRestoreController mBackupRestoreController;
  
      private final Context mContext;
  
      private IPackageManager mPackageManager;
      private AlarmManager mAlarmManager;
      private UserManager mUserManager;
      private AppOpsManager mAppOpsManager;
      private KeyguardManager mKeyguardManager;
      private DevicePolicyManagerInternal mDevicePolicyManagerInternal;
      private PackageManagerInternal mPackageManagerInternal;
  
      private SecurityPolicy mSecurityPolicy;
  
      private Handler mSaveStateHandler;
      private Handler mCallbackHandler;
  
      private Locale mLocale;
  
      private final SparseIntArray mNextAppWidgetIds = new SparseIntArray();
  
      private boolean mSafeMode;
      private int mMaxWidgetBitmapMemory;
  
      private IconUtilities mIconUtilities;
  
      AppWidgetServiceImpl(Context context) {
          mContext = context;
      }
  
      public void onStart() {
          mPackageManager = AppGlobals.getPackageManager();
          mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
          mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
          mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
          mKeyguardManager = (KeyguardManager) mContext.getSystemService(KEYGUARD_SERVICE);
          mDevicePolicyManagerInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
          mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
          mSaveStateHandler = BackgroundThread.getHandler();
          mCallbackHandler = new CallbackHandler(mContext.getMainLooper());
          mBackupRestoreController = new BackupRestoreController();
          mSecurityPolicy = new SecurityPolicy();
          mIconUtilities = new IconUtilities(mContext);
  
          computeMaximumWidgetBitmapMemory();
          registerBroadcastReceiver();
          registerOnCrossProfileProvidersChangedListener();
  
          LocalServices.addService(AppWidgetManagerInternal.class, new AppWidgetManagerLocal());
      }
  
      private void computeMaximumWidgetBitmapMemory() {
          WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
          Display display = wm.getDefaultDisplay();
          Point size = new Point();
          display.getRealSize(size);
          // Cap memory usage at 1.5 times the size of the display
          // 1.5 * 4 bytes/pixel * w * h ==> 6 * w * h
          mMaxWidgetBitmapMemory = 6 * size.x * size.y;
      }
  
      private void registerBroadcastReceiver() {
          // Register for configuration changes so we can update the names
          // of the widgets when the locale changes.
          IntentFilter configFilter = new IntentFilter();
          configFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
          mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
                  configFilter, null, null);
  
          // Register for broadcasts about package install, etc., so we can
          // update the provider list.
          IntentFilter packageFilter = new IntentFilter();
          packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
          packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
          packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
          packageFilter.addDataScheme("package");
          mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
                  packageFilter, null, null);
  
          // Register for events related to sdcard installation.
          IntentFilter sdFilter = new IntentFilter();
          sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
          sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
          mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
                  sdFilter, null, null);
  
          IntentFilter offModeFilter = new IntentFilter();
          offModeFilter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
          offModeFilter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
          mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
                  offModeFilter, null, null);
  
          IntentFilter suspendPackageFilter = new IntentFilter();
          suspendPackageFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
          suspendPackageFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
          mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
                  suspendPackageFilter, null, null);
      }
  
      private void registerOnCrossProfileProvidersChangedListener() {
          // The device policy is an optional component.
          if (mDevicePolicyManagerInternal != null) {
              mDevicePolicyManagerInternal.addOnCrossProfileWidgetProvidersChangeListener(this);
          }
      }
  
      public void setSafeMode(boolean safeMode) {
          mSafeMode = safeMode;
      }
  
      private void onConfigurationChanged() {
          if (DEBUG) {
              Slog.i(TAG, "onConfigurationChanged()");
          }
  
          Locale revised = Locale.getDefault();
          if (revised == null || mLocale == null || !revised.equals(mLocale)) {
              mLocale = revised;
  
              synchronized (mLock) {
                  SparseIntArray changedGroups = null;
  
                  // Note: updateProvidersForPackageLocked() may remove providers, so we must copy the
                  // list of installed providers and skip providers that we don't need to update.
                  // Also note that remove the provider does not clear the Provider component data.
                  ArrayList<Provider> installedProviders = new ArrayList<>(mProviders);
                  HashSet<ProviderId> removedProviders = new HashSet<>();
  
                  int N = installedProviders.size();
                  for (int i = N - 1; i >= 0; i--) {
                      Provider provider = installedProviders.get(i);
  
                      final int userId = provider.getUserId();
                      if (!mUserManager.isUserUnlockingOrUnlocked(userId) ||
                              isProfileWithLockedParent(userId)) {
                          continue;
                      }
                      ensureGroupStateLoadedLocked(userId);
  
                      if (!removedProviders.contains(provider.id)) {
                          final boolean changed = updateProvidersForPackageLocked(
                                  provider.id.componentName.getPackageName(),
                                  provider.getUserId(), removedProviders);
  
                          if (changed) {
                              if (changedGroups == null) {
                                  changedGroups = new SparseIntArray();
                              }
                              final int groupId = mSecurityPolicy.getGroupParent(
                                      provider.getUserId());
                              changedGroups.put(groupId, groupId);
                          }
                      }
                  }
  
                  if (changedGroups != null) {
                      final int groupCount = changedGroups.size();
                      for (int i = 0; i < groupCount; i++) {
                          final int groupId = changedGroups.get(i);
                          saveGroupStateAsync(groupId);
                      }
                  }
              }
          }
      }
  
      private void onPackageBroadcastReceived(Intent intent, int userId) {
          final String action = intent.getAction();
          boolean added = false;
          boolean changed = false;
          boolean componentsModified = false;
  
          final String pkgList[];
          switch (action) {
              case Intent.ACTION_PACKAGES_SUSPENDED:
              case Intent.ACTION_PACKAGES_UNSUSPENDED:
                  pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
                  changed = true;
                  break;
              case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
                  added = true;
                  // Follow through
              case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
                  pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
                  break;
              default: {
                  Uri uri = intent.getData();
                  if (uri == null) {
                      return;
                  }
                  String pkgName = uri.getSchemeSpecificPart();
                  if (pkgName == null) {
                      return;
                  }
                  pkgList = new String[] { pkgName };
                  added = Intent.ACTION_PACKAGE_ADDED.equals(action);
                  changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
              }
          }
          if (pkgList == null || pkgList.length == 0) {
              return;
          }
  
          synchronized (mLock) {
              if (!mUserManager.isUserUnlockingOrUnlocked(userId) ||
                      isProfileWithLockedParent(userId)) {
                  return;
              }
              ensureGroupStateLoadedLocked(userId, /* enforceUserUnlockingOrUnlocked */ false);
  
              Bundle extras = intent.getExtras();
  
              if (added || changed) {
                  final boolean newPackageAdded = added && (extras == null
                          || !extras.getBoolean(Intent.EXTRA_REPLACING, false));
  
                  for (String pkgName : pkgList) {
                      // Fix up the providers - add/remove/update.
                      componentsModified |= updateProvidersForPackageLocked(pkgName, userId, null);
  
                      // ... and see if these are hosts we've been awaiting.
                      // NOTE: We are backing up and restoring only the owner.
                      // TODO: http://b/22388012
                      if (newPackageAdded && userId == UserHandle.USER_SYSTEM) {
                          final int uid = getUidForPackage(pkgName, userId);
                          if (uid >= 0 ) {
                              resolveHostUidLocked(pkgName, uid);
                          }
                      }
                  }
              } else {
                  // If the package is being updated, we'll receive a PACKAGE_ADDED
                  // shortly, otherwise it is removed permanently.
                  final boolean packageRemovedPermanently = (extras == null
                          || !extras.getBoolean(Intent.EXTRA_REPLACING, false));
  
                  if (packageRemovedPermanently) {
                      for (String pkgName : pkgList) {
                          componentsModified |= removeHostsAndProvidersForPackageLocked(
                                  pkgName, userId);
                      }
                  }
              }
  
              if (componentsModified) {
                  saveGroupStateAsync(userId);
  
                  // If the set of providers has been modified, notify each active AppWidgetHost
                  scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
              }
          }
      }
  
      /**
       * Reload all widgets' masked state for the given user and its associated profiles, including
       * due to user not being available and package suspension.
       * userId must be the group parent.
       */
      void reloadWidgetsMaskedStateForGroup(int userId) {
          if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
              return;
          }
          synchronized (mLock) {
              reloadWidgetsMaskedState(userId);
              int[] profileIds = mUserManager.getEnabledProfileIds(userId);
              for (int profileId : profileIds) {
                  reloadWidgetsMaskedState(profileId);
              }
          }
      }
  
      private void reloadWidgetsMaskedState(int userId) {
          final long identity = Binder.clearCallingIdentity();
          try {
              UserInfo user  = mUserManager.getUserInfo(userId);
  
              boolean lockedProfile = !mUserManager.isUserUnlockingOrUnlocked(userId);
              boolean quietProfile = user.isQuietModeEnabled();
              final int N = mProviders.size();
              for (int i = 0; i < N; i++) {
                  Provider provider = mProviders.get(i);
                  int providerUserId = provider.getUserId();
                  if (providerUserId != userId) {
                      continue;
                  }
  
                  boolean changed = provider.setMaskedByLockedProfileLocked(lockedProfile);
                  changed |= provider.setMaskedByQuietProfileLocked(quietProfile);
                  try {
                      boolean suspended;
                      try {
                          suspended = mPackageManager.isPackageSuspendedForUser(
                                  provider.info.provider.getPackageName(), provider.getUserId());
                      } catch (IllegalArgumentException ex) {
                          // Package not found.
                          suspended = false;
                      }
                      changed |= provider.setMaskedBySuspendedPackageLocked(suspended);
                  } catch (RemoteException e) {
                      Slog.e(TAG, "Failed to query application info", e);
                  }
                  if (changed) {
                      if (provider.isMaskedLocked()) {
                          maskWidgetsViewsLocked(provider, null);
                      } else {
                          unmaskWidgetsViewsLocked(provider);
                      }
                  }
              }
          } finally {
              Binder.restoreCallingIdentity(identity);
          }
      }
  
      /**
       * Incrementally update the masked state due to package suspension state.
       */
      private void updateWidgetPackageSuspensionMaskedState(Intent intent, boolean suspended,
              int profileId) {
          String[] packagesArray = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
          if (packagesArray == null) {
              return;
          }
          Set<String> packages = new ArraySet<>(Arrays.asList(packagesArray));
          synchronized (mLock) {
              final int N = mProviders.size();
              for (int i = 0; i < N; i++) {
                  Provider provider = mProviders.get(i);
                  int providerUserId = provider.getUserId();
                  if (providerUserId != profileId
                          || !packages.contains(provider.info.provider.getPackageName())) {
                      continue;
                  }
                  if (provider.setMaskedBySuspendedPackageLocked(suspended)) {
                      if (provider.isMaskedLocked()) {
                          maskWidgetsViewsLocked(provider, null);
                      } else {
                          unmaskWidgetsViewsLocked(provider);
                      }
                  }
              }
          }
      }
  
      private Bitmap createMaskedWidgetBitmap(String providerPackage, int providerUserId) {
          final long identity = Binder.clearCallingIdentity();
          try {
              // Load the unbadged application icon and pass it to the widget to appear on
              // the masked view.
              Context userContext = mContext.createPackageContextAsUser(providerPackage, 0,
                      UserHandle.of(providerUserId));
              PackageManager pm = userContext.getPackageManager();
              Drawable icon = pm.getApplicationInfo(providerPackage, 0).loadUnbadgedIcon(pm).mutate();
              // Create a bitmap of the icon which is what the widget's remoteview requires.
              icon.setColorFilter(mIconUtilities.getDisabledColorFilter());
              return mIconUtilities.createIconBitmap(icon);
          } catch (NameNotFoundException e) {
              Slog.e(TAG, "Fail to get application icon", e);
              // Provider package removed, no need to mask its views as its state will be
              // purged very soon.
              return null;
          } finally {
              Binder.restoreCallingIdentity(identity);
          }
      }
  
      private RemoteViews createMaskedWidgetRemoteViews(Bitmap icon, boolean showBadge,
              PendingIntent onClickIntent) {
          RemoteViews views = new RemoteViews(mContext.getPackageName(),
                  R.layout.work_widget_mask_view);
          if (icon != null) {
              views.setImageViewBitmap(R.id.work_widget_app_icon, icon);
          }
          if (!showBadge) {
              views.setViewVisibility(R.id.work_widget_badge_icon, View.INVISIBLE);
          }
          if (onClickIntent != null) {
              views.setOnClickPendingIntent(R.id.work_widget_mask_frame, onClickIntent);
          }
          return views;
      }
  
      /**
       * Mask the target widget belonging to the specified provider, or all active widgets
       * of the provider if target widget == null.
       */
      private void maskWidgetsViewsLocked(Provider provider, Widget targetWidget) {
          final int widgetCount = provider.widgets.size();
          if (widgetCount == 0) {
              return;
          }
          final String providerPackage = provider.info.provider.getPackageName();
          final int providerUserId = provider.getUserId();
          Bitmap iconBitmap = createMaskedWidgetBitmap(providerPackage, providerUserId);
          if (iconBitmap == null) {
              return;
          }
          final boolean showBadge;
          final Intent onClickIntent;
          final long identity = Binder.clearCallingIdentity();
          try {
              if (provider.maskedBySuspendedPackage) {
                  UserInfo userInfo = mUserManager.getUserInfo(providerUserId);
                  showBadge = userInfo.isManagedProfile();
                  final String suspendingPackage = mPackageManagerInternal.getSuspendingPackage(
                          providerPackage, providerUserId);
                  if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) {
                      onClickIntent = mDevicePolicyManagerInternal.createShowAdminSupportIntent(
                              providerUserId, true);
                  } else {
                      final SuspendDialogInfo dialogInfo = mPackageManagerInternal
                              .getSuspendedDialogInfo(providerPackage, providerUserId);
                      onClickIntent = SuspendedAppActivity.createSuspendedAppInterceptIntent(
                              providerPackage, suspendingPackage, dialogInfo, providerUserId);
                  }
              } else if (provider.maskedByQuietProfile) {
                  showBadge = true;
                  onClickIntent = UnlaunchableAppActivity.createInQuietModeDialogIntent(
                          providerUserId);
              } else /* provider.maskedByLockedProfile */ {
                  showBadge = true;
                  onClickIntent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null,
                          providerUserId);
                  if (onClickIntent != null) {
                      onClickIntent.setFlags(FLAG_ACTIVITY_NEW_TASK
                              | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
                  }
              }
              for (int j = 0; j < widgetCount; j++) {
                  Widget widget = provider.widgets.get(j);
                  if (targetWidget != null && targetWidget != widget) continue;
                  PendingIntent intent = null;
                  if (onClickIntent != null) {
                      intent = PendingIntent.getActivity(mContext, widget.appWidgetId,
                              onClickIntent, PendingIntent.FLAG_UPDATE_CURRENT);
                  }
                  RemoteViews views = createMaskedWidgetRemoteViews(iconBitmap, showBadge, intent);
                  if (widget.replaceWithMaskedViewsLocked(views)) {
                      scheduleNotifyUpdateAppWidgetLocked(widget, widget.getEffectiveViewsLocked());
                  }
              }
          } finally {
              Binder.restoreCallingIdentity(identity);
          }
      }
  
      private void unmaskWidgetsViewsLocked(Provider provider) {
          final int widgetCount = provider.widgets.size();
          for (int j = 0; j < widgetCount; j++) {
              Widget widget = provider.widgets.get(j);
              if (widget.clearMaskedViewsLocked()) {
                  scheduleNotifyUpdateAppWidgetLocked(widget, widget.getEffectiveViewsLocked());
              }
          }
      }
  
      private void resolveHostUidLocked(String pkg, int uid) {
          final int N = mHosts.size();
          for (int i = 0; i < N; i++) {
              Host host = mHosts.get(i);
              if (host.id.uid == UNKNOWN_UID && pkg.equals(host.id.packageName)) {
                  if (DEBUG) {
                      Slog.i(TAG, "host " + host.id + " resolved to uid " + uid);
                  }
                  host.id = new HostId(uid, host.id.hostId, host.id.packageName);
                  return;
              }
          }
      }
  
      private void ensureGroupStateLoadedLocked(int userId) {
          ensureGroupStateLoadedLocked(userId, /* enforceUserUnlockingOrUnlocked */ true );
      }
  
      private void ensureGroupStateLoadedLocked(int userId, boolean enforceUserUnlockingOrUnlocked) {
          if (enforceUserUnlockingOrUnlocked && !isUserRunningAndUnlocked(userId)) {
              throw new IllegalStateException(
                      "User " + userId + " must be unlocked for widgets to be available");
          }
          if (enforceUserUnlockingOrUnlocked && isProfileWithLockedParent(userId)) {
              throw new IllegalStateException(
                      "Profile " + userId + " must have unlocked parent");
          }
          final int[] profileIds = mSecurityPolicy.getEnabledGroupProfileIds(userId);
  
          // Careful lad, we may have already loaded the state for some
          // group members, so check before loading and read only the
          // state for the new member(s).
          int newMemberCount = 0;
          final int profileIdCount = profileIds.length;
          for (int i = 0; i < profileIdCount; i++) {
              final int profileId = profileIds[i];
              if (mLoadedUserIds.indexOfKey(profileId) >= 0) {
                  profileIds[i] = LOADED_PROFILE_ID;
              } else {
                  newMemberCount++;
              }
          }
  
          if (newMemberCount <= 0) {
              return;
          }
  
          int newMemberIndex = 0;
          final int[] newProfileIds = new int[newMemberCount];
          for (int i = 0; i < profileIdCount; i++) {
              final int profileId = profileIds[i];
              if (profileId != LOADED_PROFILE_ID) {
                  mLoadedUserIds.put(profileId, profileId);
                  newProfileIds[newMemberIndex] = profileId;
                  newMemberIndex++;
              }
          }
  
          clearProvidersAndHostsTagsLocked();
  
          loadGroupWidgetProvidersLocked(newProfileIds);
          loadGroupStateLocked(newProfileIds);
      }
  
      private boolean isUserRunningAndUnlocked(@UserIdInt int userId) {
          return mUserManager.isUserUnlockingOrUnlocked(userId);
      }
  
      @Override
      public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
          if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
  
          synchronized (mLock) {
              if (args.length > 0 && "--proto".equals(args[0])) {
                  dumpProto(fd);
              } else {
                  dumpInternal(pw);
              }
          }
      }
  
      private void dumpProto(FileDescriptor fd) {
          Slog.i(TAG, "dump proto for " + mWidgets.size() + " widgets");
  
          ProtoOutputStream proto = new ProtoOutputStream(fd);
          int N = mWidgets.size();
          for (int i=0; i < N; i++) {
              dumpProtoWidget(proto, mWidgets.get(i));
          }
          proto.flush();
      }
  
      private void dumpProtoWidget(ProtoOutputStream proto, Widget widget) {
          if (widget.host == null || widget.provider == null) {
              Slog.d(TAG, "skip dumping widget because host or provider is null: widget.host="
                  + widget.host + " widget.provider="  + widget.provider);
              return;
          }
          long token = proto.start(AppWidgetServiceDumpProto.WIDGETS);
          proto.write(WidgetProto.IS_CROSS_PROFILE,
              widget.host.getUserId() != widget.provider.getUserId());
          proto.write(WidgetProto.IS_HOST_STOPPED, widget.host.callbacks == null);
          proto.write(WidgetProto.HOST_PACKAGE, widget.host.id.packageName);
          proto.write(WidgetProto.PROVIDER_PACKAGE, widget.provider.id.componentName.getPackageName());
          proto.write(WidgetProto.PROVIDER_CLASS, widget.provider.id.componentName.getClassName());
          if (widget.options != null) {
              proto.write(WidgetProto.MIN_WIDTH,
                  widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, 0));
              proto.write(WidgetProto.MIN_HEIGHT,
                  widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, 0));
              proto.write(WidgetProto.MAX_WIDTH,
                  widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, 0));
              proto.write(WidgetProto.MAX_HEIGHT,
                  widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, 0));
          }
          proto.end(token);
      }
  
      private void dumpInternal(PrintWriter pw) {
          int N = mProviders.size();
          pw.println("Providers:");
          for (int i = 0; i < N; i++) {
              dumpProvider(mProviders.get(i), i, pw);
          }
  
          N = mWidgets.size();
          pw.println(" ");
          pw.println("Widgets:");
          for (int i = 0; i < N; i++) {
              dumpWidget(mWidgets.get(i), i, pw);
          }
  
          N = mHosts.size();
          pw.println(" ");
          pw.println("Hosts:");
          for (int i = 0; i < N; i++) {
              dumpHost(mHosts.get(i), i, pw);
          }
  
          N = mPackagesWithBindWidgetPermission.size();
          pw.println(" ");
          pw.println("Grants:");
          for (int i = 0; i < N; i++) {
              Pair<Integer, String> grant = mPackagesWithBindWidgetPermission.valueAt(i);
              dumpGrant(grant, i, pw);
          }
      }
  
      @Override
      public ParceledListSlice<PendingHostUpdate> startListening(IAppWidgetHost callbacks,
              String callingPackage, int hostId, int[] appWidgetIds) {
          final int userId = UserHandle.getCallingUserId();
  
          if (DEBUG) {
              Slog.i(TAG, "startListening() " + userId);
          }
  
          // Make sure the package runs under the caller uid.
          mSecurityPolicy.enforceCallFromPackage(callingPackage);
  
          synchronized (mLock) {
              // Instant apps cannot host app widgets.
              if (mSecurityPolicy.isInstantAppLocked(callingPackage, userId)) {
                  Slog.w(TAG, "Instant package " + callingPackage + " cannot host app widgets");
                  return ParceledListSlice.emptyList();
              }
  
              ensureGroupStateLoadedLocked(userId);
  
              // NOTE: The lookup is enforcing security across users by making
              // sure the caller can only access hosts it owns.
              HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage);
              Host host = lookupOrAddHostLocked(id);
              host.callbacks = callbacks;
  
              long updateSequenceNo = UPDATE_COUNTER.incrementAndGet();
              int N = appWidgetIds.length;
              ArrayList<PendingHostUpdate> outUpdates = new ArrayList<>(N);
              LongSparseArray<PendingHostUpdate> updatesMap = new LongSparseArray<>();
              for (int i = 0; i < N; i++) {
                  if (host.getPendingUpdatesForId(appWidgetIds[i], updatesMap)) {
                      // We key the updates based on request id, so that the values are sorted in the
                      // order they were received.
                      int M = updatesMap.size();
                      for (int j = 0; j < M; j++) {
                          outUpdates.add(updatesMap.valueAt(j));
                      }
                  }
              }
              // Reset the update counter once all the updates have been calculated
              host.lastWidgetUpdateSequenceNo = updateSequenceNo;
              return new ParceledListSlice<>(outUpdates);
          }
      }
  
      @Override
      public void stopListening(String callingPackage, int hostId) {
          final int userId = UserHandle.getCallingUserId();
  
          if (DEBUG) {
              Slog.i(TAG, "stopListening() " + userId);
          }
  
          // Make sure the package runs under the caller uid.
          mSecurityPolicy.enforceCallFromPackage(callingPackage);
  
          synchronized (mLock) {
              ensureGroupStateLoadedLocked(userId, /* enforceUserUnlockingOrUnlocked */ false);
  
              // NOTE: The lookup is enforcing security across users by making
              // sure the caller can only access hosts it owns.
              HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage);
              Host host = lookupHostLocked(id);
  
              if (host != null) {
                  host.callbacks = null;
                  pruneHostLocked(host);
              }
          }
      }
  
      @Override
      public int allocateAppWidgetId(String callingPackage, int hostId) {
          final int userId = UserHandle.getCallingUserId();
  
          if (DEBUG) {
              Slog.i(TAG, "allocateAppWidgetId() " + userId);
          }
  
          // Make sure the package runs under the caller uid.
          mSecurityPolicy.enforceCallFromPackage(callingPackage);
  
          synchronized (mLock) {
              // Instant apps cannot host app widgets.
              if (mSecurityPolicy.isInstantAppLocked(callingPackage, userId)) {
                  Slog.w(TAG, "Instant package " + callingPackage + " cannot host app widgets");
                  return AppWidgetManager.INVALID_APPWIDGET_ID;
              }
  
              ensureGroupStateLoadedLocked(userId);
  
              if (mNextAppWidgetIds.indexOfKey(userId) < 0) {
                  mNextAppWidgetIds.put(userId, AppWidgetManager.INVALID_APPWIDGET_ID + 1);
              }
  
              final int appWidgetId = incrementAndGetAppWidgetIdLocked(userId);
  
              // NOTE: The lookup is enforcing security across users by making
              // sure the caller can only access hosts it owns.
              HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage);
              Host host = lookupOrAddHostLocked(id);
  
              Widget widget = new Widget();
              widget.appWidgetId = appWidgetId;
              widget.host = host;
  
              host.widgets.add(widget);
              addWidgetLocked(widget);
  
              saveGroupStateAsync(userId);
  
              if (DEBUG) {
                  Slog.i(TAG, "Allocated widget id " + appWidgetId
                          + " for host " + host.id);
              }
  
              return appWidgetId;
          }
      }
  
      @Override
      public void deleteAppWidgetId(String callingPackage, int appWidgetId) {
          final int userId = UserHandle.getCallingUserId();
  
          if (DEBUG) {
              Slog.i(TAG, "deleteAppWidgetId() " + userId);
          }
  
          // Make sure the package runs under the caller uid.
          mSecurityPolicy.enforceCallFromPackage(callingPackage);
  
          synchronized (mLock) {
              ensureGroupStateLoadedLocked(userId);
  
              // NOTE: The lookup is enforcing security across users by making
              // sure the caller can only access widgets it hosts or provides.
              Widget widget = lookupWidgetLocked(appWidgetId,
                      Binder.getCallingUid(), callingPackage);
  
              if (widget == null) {
                  return;
              }
  
              deleteAppWidgetLocked(widget);
  
              saveGroupStateAsync(userId);
  
              if (DEBUG) {
                  Slog.i(TAG, "Deleted widget id " + appWidgetId
                          + " for host " + widget.host.id);
              }
          }
      }
  
      @Override
      public boolean hasBindAppWidgetPermission(String packageName, int grantId) {
          if (DEBUG) {
              Slog.i(TAG, "hasBindAppWidgetPermission() " + UserHandle.getCallingUserId());
          }
  
          // A special permission is required for managing white listing.
          mSecurityPolicy.enforceModifyAppWidgetBindPermissions(packageName);
  
          synchronized (mLock) {
              // The grants are stored in user state wich gets the grant.
              ensureGroupStateLoadedLocked(grantId);
  
              final int packageUid = getUidForPackage(packageName, grantId);
              if (packageUid < 0) {
                  return false;
              }
  
              Pair<Integer, String> packageId = Pair.create(grantId, packageName);
              return mPackagesWithBindWidgetPermission.contains(packageId);
          }
      }
  
      @Override
      public void setBindAppWidgetPermission(String packageName, int grantId,
              boolean grantPermission) {
          if (DEBUG) {
              Slog.i(TAG, "setBindAppWidgetPermission() " + UserHandle.getCallingUserId());
          }
  
          // A special permission is required for managing white listing.
          mSecurityPolicy.enforceModifyAppWidgetBindPermissions(packageName);
  
          synchronized (mLock) {
              // The grants are stored in user state wich gets the grant.
              ensureGroupStateLoadedLocked(grantId);
  
              final int packageUid = getUidForPackage(packageName, grantId);
              if (packageUid < 0) {
                  return;
              }
  
              Pair<Integer, String> packageId = Pair.create(grantId, packageName);
              if (grantPermission) {
                  mPackagesWithBindWidgetPermission.add(packageId);
              } else {
                  mPackagesWithBindWidgetPermission.remove(packageId);
              }
  
              saveGroupStateAsync(grantId);
          }
      }
  
      @Override
      public IntentSender createAppWidgetConfigIntentSender(String callingPackage, int appWidgetId,
              final int intentFlags) {
          final int userId = UserHandle.getCallingUserId();
  
          if (DEBUG) {
              Slog.i(TAG, "createAppWidgetConfigIntentSender() " + userId);
          }
  
          // Make sure the package runs under the caller uid.
          mSecurityPolicy.enforceCallFromPackage(callingPackage);
  
          synchronized (mLock) {
              ensureGroupStateLoadedLocked(userId);
  
              // NOTE: The lookup is enforcing security across users by making
              // sure the caller can only access widgets it hosts or provides.
              Widget widget = lookupWidgetLocked(appWidgetId,
                      Binder.getCallingUid(), callingPackage);
  
              if (widget == null) {
                  throw new IllegalArgumentException("Bad widget id " + appWidgetId);
              }
  
              Provider provider = widget.provider;
              if (provider == null) {
                  throw new IllegalArgumentException("Widget not bound " + appWidgetId);
              }
  
              // Make sure only safe flags can be passed it.
              final int secureFlags = intentFlags & ~Intent.IMMUTABLE_FLAGS;
  
              Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
              intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
              intent.setComponent(provider.info.configure);
              intent.setFlags(secureFlags);
  
              // All right, create the sender.
              final long identity = Binder.clearCallingIdentity();
              try {
                  return PendingIntent.getActivityAsUser(
                          mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT
                                  | PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_CANCEL_CURRENT,
                                  null, new UserHandle(provider.getUserId()))
                          .getIntentSender();
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
      }
  
      @Override
      public boolean bindAppWidgetId(String callingPackage, int appWidgetId,
              int providerProfileId, ComponentName providerComponent, Bundle options) {
          final int userId = UserHandle.getCallingUserId();
  
          if (DEBUG) {
              Slog.i(TAG, "bindAppWidgetId() " + userId);
          }
  
          // Make sure the package runs under the caller uid.
          mSecurityPolicy.enforceCallFromPackage(callingPackage);
  
          // Check that if a cross-profile binding is attempted, it is allowed.
          if (!mSecurityPolicy.isEnabledGroupProfile(providerProfileId)) {
              return false;
          }
  
          // If the provider is not under the calling user, make sure this
          // provider is white listed for access from the parent.
          if (!mSecurityPolicy.isProviderInCallerOrInProfileAndWhitelListed(
                  providerComponent.getPackageName(), providerProfileId)) {
              return false;
          }
  
          synchronized (mLock) {
              ensureGroupStateLoadedLocked(userId);
  
              // A special permission or white listing is required to bind widgets.
              if (!mSecurityPolicy.hasCallerBindPermissionOrBindWhiteListedLocked(
                      callingPackage)) {
                  return false;
              }
  
              // NOTE: The lookup is enforcing security across users by making
              // sure the caller can only access widgets it hosts or provides.
              Widget widget = lookupWidgetLocked(appWidgetId,
                      Binder.getCallingUid(), callingPackage);
  
              if (widget == null) {
                  Slog.e(TAG, "Bad widget id " + appWidgetId);
                  return false;
              }
  
              if (widget.provider != null) {
                  Slog.e(TAG, "Widget id " + appWidgetId
                          + " already bound to: " + widget.provider.id);
                  return false;
              }
  
              final int providerUid = getUidForPackage(providerComponent.getPackageName(),
                      providerProfileId);
              if (providerUid < 0) {
                  Slog.e(TAG, "Package " + providerComponent.getPackageName() + " not installed "
                          + " for profile " + providerProfileId);
                  return false;
              }
  
              // NOTE: The lookup is enforcing security across users by making
              // sure the provider is in the already vetted user profile.
              ProviderId providerId = new ProviderId(providerUid, providerComponent);
              Provider provider = lookupProviderLocked(providerId);
  
              if (provider == null) {
                  Slog.e(TAG, "No widget provider " + providerComponent + " for profile "
                          + providerProfileId);
                  return false;
              }
  
              if (provider.zombie) {
                  Slog.e(TAG, "Can't bind to a 3rd party provider in"
                          + " safe mode " + provider);
                  return false;
              }
  
              widget.provider = provider;
              widget.options = (options != null) ? cloneIfLocalBinder(options) : new Bundle();
  
              // We need to provide a default value for the widget category if it is not specified
              if (!widget.options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) {
                  widget.options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
                          AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN);
              }
  
              provider.widgets.add(widget);
  
              onWidgetProviderAddedOrChangedLocked(widget);
  
              final int widgetCount = provider.widgets.size();
              if (widgetCount == 1) {
                  // Tell the provider that it's ready.
                  sendEnableIntentLocked(provider);
              }
  
              // Send an update now -- We need this update now, and just for this appWidgetId.
              // It's less critical when the next one happens, so when we schedule the next one,
              // we add updatePeriodMillis to its start time. That time will have some slop,
              // but that's okay.
              sendUpdateIntentLocked(provider, new int[] {appWidgetId});
  
              // Schedule the future updates.
              registerForBroadcastsLocked(provider, getWidgetIds(provider.widgets));
  
              saveGroupStateAsync(userId);
  
              if (DEBUG) {
                  Slog.i(TAG, "Bound widget " + appWidgetId + " to provider " + provider.id);
              }
          }
  
          return true;
      }
  
      @Override
      public int[] getAppWidgetIds(ComponentName componentName) {
          final int userId = UserHandle.getCallingUserId();
  
          if (DEBUG) {
              Slog.i(TAG, "getAppWidgetIds() " + userId);
          }
  
          // Make sure the package runs under the caller uid.
          mSecurityPolicy.enforceCallFromPackage(componentName.getPackageName());
  
          synchronized (mLock) {
              ensureGroupStateLoadedLocked(userId);
  
              // NOTE: The lookup is enforcing security across users by making
              // sure the caller can access only its providers.
              ProviderId providerId = new ProviderId(Binder.getCallingUid(), componentName);
              Provider provider = lookupProviderLocked(providerId);
  
              if (provider != null) {
                  return getWidgetIds(provider.widgets);
              }
  
              return new int[0];
          }
      }
  
      @Override
      public int[] getAppWidgetIdsForHost(String callingPackage, int hostId) {
          final int userId = UserHandle.getCallingUserId();
  
          if (DEBUG) {
              Slog.i(TAG, "getAppWidgetIdsForHost() " + userId);
          }
  
          // Make sure the package runs under the caller uid.
          mSecurityPolicy.enforceCallFromPackage(callingPackage);
  
          synchronized (mLock) {
              ensureGroupStateLoadedLocked(userId);
  
              // NOTE: The lookup is enforcing security across users by making
              // sure the caller can only access its hosts.
              HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage);
              Host host = lookupHostLocked(id);
  
              if (host != null) {
                  return getWidgetIds(host.widgets);
              }
  
              return new int[0];
          }
      }
  
      @Override
      public boolean bindRemoteViewsService(String callingPackage, int appWidgetId, Intent intent,
              IApplicationThread caller, IBinder activtiyToken, IServiceConnection connection,
              int flags) {
          final int userId = UserHandle.getCallingUserId();
          if (DEBUG) {
              Slog.i(TAG, "bindRemoteViewsService() " + userId);
          }
  
          synchronized (mLock) {
              ensureGroupStateLoadedLocked(userId);
  
              // NOTE: The lookup is enforcing security across users by making
              // sure the caller can only access widgets it hosts or provides.
              Widget widget = lookupWidgetLocked(appWidgetId,
                      Binder.getCallingUid(), callingPackage);
  
              if (widget == null) {
                  throw new IllegalArgumentException("Bad widget id");
              }
  
              // Make sure the widget has a provider.
              if (widget.provider == null) {
                  throw new IllegalArgumentException("No provider for widget "
                          + appWidgetId);
              }
  
              ComponentName componentName = intent.getComponent();
  
              // Ensure that the service belongs to the same package as the provider.
              // But this is not enough as they may be under different users - see below...
              String providerPackage = widget.provider.id.componentName.getPackageName();
              String servicePackage = componentName.getPackageName();
              if (!servicePackage.equals(providerPackage)) {
                  throw new SecurityException("The taget service not in the same package"
                          + " as the widget provider");
              }
  
              // Make sure this service exists under the same user as the provider and
              // requires a permission which allows only the system to bind to it.
              mSecurityPolicy.enforceServiceExistsAndRequiresBindRemoteViewsPermission(
                      componentName, widget.provider.getUserId());
  
              // Good to go - the service package is correct, it exists for the correct
              // user, and requires the bind permission.
  
              final long callingIdentity = Binder.clearCallingIdentity();
              try {
                  // Ask ActivityManager to bind it. Notice that we are binding the service with the
                  // caller app instead of DevicePolicyManagerService.
                  if(ActivityManager.getService().bindService(
                          caller, activtiyToken, intent,
                          intent.resolveTypeIfNeeded(mContext.getContentResolver()),
                          connection, flags, mContext.getOpPackageName(),
                          widget.provider.getUserId()) != 0) {
  
                      // Add it to the mapping of RemoteViewsService to appWidgetIds so that we
                      // can determine when we can call back to the RemoteViewsService later to
                      // destroy associated factories.
                      incrementAppWidgetServiceRefCount(appWidgetId,
                              Pair.create(widget.provider.id.uid, new FilterComparison(intent)));
                      return true;
                  }
              } catch (RemoteException ex) {
                  // Same process, should not happen.
              } finally {
                  Binder.restoreCallingIdentity(callingIdentity);
              }
          }
  
          // Failed to bind.
          return false;
      }
  
      @Override
      public void deleteHost(String callingPackage, int hostId) {
          final int userId = UserHandle.getCallingUserId();
  
          if (DEBUG) {
              Slog.i(TAG, "deleteHost() " + userId);
          }
  
          // Make sure the package runs under the caller uid.
          mSecurityPolicy.enforceCallFromPackage(callingPackage);
  
          synchronized (mLock) {
              ensureGroupStateLoadedLocked(userId);
  
              // NOTE: The lookup is enforcing security across users by making
              // sure the caller can only access hosts in its uid and package.
              HostId id = new HostId(Binder.getCallingUid(), hostId, callingPackage);
              Host host = lookupHostLocked(id);
  
              if (host == null) {
                  return;
              }
  
              deleteHostLocked(host);
  
              saveGroupStateAsync(userId);
  
              if (DEBUG) {
                  Slog.i(TAG, "Deleted host " + host.id);
              }
          }
      }
  
      @Override
      public void deleteAllHosts() {
          final int userId = UserHandle.getCallingUserId();
  
          if (DEBUG) {
              Slog.i(TAG, "deleteAllHosts() " + userId);
          }
  
          synchronized (mLock) {
              ensureGroupStateLoadedLocked(userId);
  
              boolean changed = false;
  
              final int N = mHosts.size();
              for (int i = N - 1; i >= 0; i--) {
                  Host host = mHosts.get(i);
  
                  // Delete only hosts in the calling uid.
                  if (host.id.uid == Binder.getCallingUid()) {
                      deleteHostLocked(host);
                      changed = true;
  
                      if (DEBUG) {
                          Slog.i(TAG, "Deleted host " + host.id);
                      }
                  }
              }
  
              if (changed) {
                  saveGroupStateAsync(userId);
              }
          }
      }
  
      @Override
      public AppWidgetProviderInfo getAppWidgetInfo(String callingPackage, int appWidgetId) {
          final int userId = UserHandle.getCallingUserId();
  
          if (DEBUG) {
              Slog.i(TAG, "getAppWidgetInfo() " + userId);
          }
  
          // Make sure the package runs under the caller uid.
          mSecurityPolicy.enforceCallFromPackage(callingPackage);
  
          synchronized (mLock) {
              ensureGroupStateLoadedLocked(userId);
  
              // NOTE: The lookup is enforcing security across users by making
              // sure the caller can only access widgets it hosts or provides.
              Widget widget = lookupWidgetLocked(appWidgetId,
                      Binder.getCallingUid(), callingPackage);
  
              if (widget != null && widget.provider != null && !widget.provider.zombie) {
                  return cloneIfLocalBinder(widget.provider.info);
              }
  
              return null;
          }
      }
  
      @Override
      public RemoteViews getAppWidgetViews(String callingPackage, int appWidgetId) {
          final int userId = UserHandle.getCallingUserId();
  
          if (DEBUG) {
              Slog.i(TAG, "getAppWidgetViews() " + userId);
          }
  
          // Make sure the package runs under the caller uid.
          mSecurityPolicy.enforceCallFromPackage(callingPackage);
  
          synchronized (mLock) {
              ensureGroupStateLoadedLocked(userId);
  
              // NOTE: The lookup is enforcing security across users by making
              // sure the caller can only access widgets it hosts or provides.
              Widget widget = lookupWidgetLocked(appWidgetId,
                      Binder.getCallingUid(), callingPackage);
  
              if (widget != null) {
                  return cloneIfLocalBinder(widget.getEffectiveViewsLocked());
              }
  
              return null;
          }
      }
  
      @Override
      public void updateAppWidgetOptions(String callingPackage, int appWidgetId, Bundle options) {
          final int userId = UserHandle.getCallingUserId();
  
          if (DEBUG) {
              Slog.i(TAG, "updateAppWidgetOptions() " + userId);
          }
  
          // Make sure the package runs under the caller uid.
          mSecurityPolicy.enforceCallFromPackage(callingPackage);
  
          synchronized (mLock) {
              ensureGroupStateLoadedLocked(userId);
  
              // NOTE: The lookup is enforcing security across users by making
              // sure the caller can only access widgets it hosts or provides.
              Widget widget = lookupWidgetLocked(appWidgetId,
                      Binder.getCallingUid(), callingPackage);
  
              if (widget == null) {
                  return;
              }
  
              // Merge the options.
              widget.options.putAll(options);
  
              // Send the broacast to notify the provider that options changed.
              sendOptionsChangedIntentLocked(widget);
  
              saveGroupStateAsync(userId);
          }
      }
  
      @Override
      public Bundle getAppWidgetOptions(String callingPackage, int appWidgetId) {
          final int userId = UserHandle.getCallingUserId();
  
          if (DEBUG) {
              Slog.i(TAG, "getAppWidgetOptions() " + userId);
          }
  
          // Make sure the package runs under the caller uid.
          mSecurityPolicy.enforceCallFromPackage(callingPackage);
  
          synchronized (mLock) {
              ensureGroupStateLoadedLocked(userId);
  
              // NOTE: The lookup is enforcing security across users by making
              // sure the caller can only access widgets it hosts or provides.
              Widget widget = lookupWidgetLocked(appWidgetId,
                      Binder.getCallingUid(), callingPackage);
  
              if (widget != null && widget.options != null) {
                  return cloneIfLocalBinder(widget.options);
              }
  
              return Bundle.EMPTY;
          }
      }
  
      @Override
      public void updateAppWidgetIds(String callingPackage, int[] appWidgetIds,
              RemoteViews views) {
          if (DEBUG) {
              Slog.i(TAG, "updateAppWidgetIds() " + UserHandle.getCallingUserId());
          }
  
          updateAppWidgetIds(callingPackage, appWidgetIds, views, false);
      }
  
      @Override
      public void partiallyUpdateAppWidgetIds(String callingPackage, int[] appWidgetIds,
              RemoteViews views) {
          if (DEBUG) {
              Slog.i(TAG, "partiallyUpdateAppWidgetIds() " + UserHandle.getCallingUserId());
          }
  
          updateAppWidgetIds(callingPackage, appWidgetIds, views, true);
      }
  
      @Override
      public void notifyAppWidgetViewDataChanged(String callingPackage, int[] appWidgetIds,
              int viewId) {
          final int userId = UserHandle.getCallingUserId();
  
          if (DEBUG) {
              Slog.i(TAG, "notifyAppWidgetViewDataChanged() " + userId);
          }
  
          // Make sure the package runs under the caller uid.
          mSecurityPolicy.enforceCallFromPackage(callingPackage);
  
          if (appWidgetIds == null || appWidgetIds.length == 0) {
              return;
          }
  
          synchronized (mLock) {
              ensureGroupStateLoadedLocked(userId);
  
              final int N = appWidgetIds.length;
              for (int i = 0; i < N; i++) {
                  final int appWidgetId = appWidgetIds[i];
  
                  // NOTE: The lookup is enforcing security across users by making
                  // sure the caller can only access widgets it hosts or provides.
                  Widget widget = lookupWidgetLocked(appWidgetId,
                          Binder.getCallingUid(), callingPackage);
  
                  if (widget != null) {
                      scheduleNotifyAppWidgetViewDataChanged(widget, viewId);
                  }
              }
          }
      }
  
      @Override
      public void updateAppWidgetProvider(ComponentName componentName, RemoteViews views) {
          final int userId = UserHandle.getCallingUserId();
  
          if (DEBUG) {
              Slog.i(TAG, "updateAppWidgetProvider() " + userId);
          }
  
          // Make sure the package runs under the caller uid.
          mSecurityPolicy.enforceCallFromPackage(componentName.getPackageName());
  
          synchronized (mLock) {
              ensureGroupStateLoadedLocked(userId);
  
              // NOTE: The lookup is enforcing security across users by making
              // sure the caller can access only its providers.
              ProviderId providerId = new ProviderId(Binder.getCallingUid(), componentName);
              Provider provider = lookupProviderLocked(providerId);
  
              if (provider == null) {
                  Slog.w(TAG, "Provider doesn't exist " + providerId);
                  return;
              }
  
              ArrayList<Widget> instances = provider.widgets;
              final int N = instances.size();
              for (int i = 0; i < N; i++) {
                  Widget widget = instances.get(i);
                  updateAppWidgetInstanceLocked(widget, views, false);
              }
          }
      }
  
      @Override
      public void updateAppWidgetProviderInfo(ComponentName componentName, String metadataKey) {
          final int userId = UserHandle.getCallingUserId();
          if (DEBUG) {
              Slog.i(TAG, "updateAppWidgetProvider() " + userId);
          }
  
          // Make sure the package runs under the caller uid.
          mSecurityPolicy.enforceCallFromPackage(componentName.getPackageName());
  
          synchronized (mLock) {
              ensureGroupStateLoadedLocked(userId);
  
              // NOTE: The lookup is enforcing security across users by making
              // sure the caller can access only its providers.
              ProviderId providerId = new ProviderId(Binder.getCallingUid(), componentName);
              Provider provider = lookupProviderLocked(providerId);
              if (provider == null) {
                  throw new IllegalArgumentException(
                          componentName + " is not a valid AppWidget provider");
              }
              if (Objects.equals(provider.infoTag, metadataKey)) {
                  // No change
                  return;
              }
  
              String keyToUse = metadataKey == null
                      ? AppWidgetManager.META_DATA_APPWIDGET_PROVIDER : metadataKey;
              AppWidgetProviderInfo info =
                      parseAppWidgetProviderInfo(providerId, provider.info.providerInfo, keyToUse);
              if (info == null) {
                  throw new IllegalArgumentException("Unable to parse " + keyToUse
                          + " meta-data to a valid AppWidget provider");
              }
  
              provider.info = info;
              provider.infoTag = metadataKey;
  
              // Update all widgets for this provider
              final int N = provider.widgets.size();
              for (int i = 0; i < N; i++) {
                  Widget widget = provider.widgets.get(i);
                  scheduleNotifyProviderChangedLocked(widget);
                  updateAppWidgetInstanceLocked(widget, widget.views, false /* isPartialUpdate */);
              }
  
              saveGroupStateAsync(userId);
              scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
          }
      }
  
      @Override
      public boolean isRequestPinAppWidgetSupported() {
          synchronized (mLock) {
              if (mSecurityPolicy.isCallerInstantAppLocked()) {
                  Slog.w(TAG, "Instant uid " + Binder.getCallingUid()
                          + " query information about app widgets");
                  return false;
              }
          }
          return LocalServices.getService(ShortcutServiceInternal.class)
                  .isRequestPinItemSupported(UserHandle.getCallingUserId(),
                          LauncherApps.PinItemRequest.REQUEST_TYPE_APPWIDGET);
      }
  
      @Override
      public boolean requestPinAppWidget(String callingPackage, ComponentName componentName,
              Bundle extras, IntentSender resultSender) {
          final int callingUid = Binder.getCallingUid();
          final int userId = UserHandle.getUserId(callingUid);
  
          if (DEBUG) {
              Slog.i(TAG, "requestPinAppWidget() " + userId);
          }
  
          final AppWidgetProviderInfo info;
  
          synchronized (mLock) {
              ensureGroupStateLoadedLocked(userId);
  
              // Look for the widget associated with the caller.
              Provider provider = lookupProviderLocked(new ProviderId(callingUid, componentName));
              if (provider == null || provider.zombie) {
                  return false;
              }
              info = provider.info;
              if ((info.widgetCategory & AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN) == 0) {
                  return false;
              }
          }
  
          return LocalServices.getService(ShortcutServiceInternal.class)
                  .requestPinAppWidget(callingPackage, info, extras, resultSender, userId);
      }
  
      @Override
      public ParceledListSlice<AppWidgetProviderInfo> getInstalledProvidersForProfile(int categoryFilter,
              int profileId, String packageName) {
          final int userId = UserHandle.getCallingUserId();
  
          if (DEBUG) {
              Slog.i(TAG, "getInstalledProvidersForProfiles() " + userId);
          }
  
          // Ensure the profile is in the group and enabled.
          if (!mSecurityPolicy.isEnabledGroupProfile(profileId)) {
              return null;
          }
  
          synchronized (mLock) {
              if (mSecurityPolicy.isCallerInstantAppLocked()) {
                  Slog.w(TAG, "Instant uid " + Binder.getCallingUid()
                          + " cannot access widget providers");
                  return ParceledListSlice.emptyList();
              }
  
              ensureGroupStateLoadedLocked(userId);
  
              ArrayList<AppWidgetProviderInfo> result = new ArrayList<AppWidgetProviderInfo>();
  
              final int providerCount = mProviders.size();
              for (int i = 0; i < providerCount; i++) {
                  Provider provider = mProviders.get(i);
                  AppWidgetProviderInfo info = provider.info;
  
                  // Ignore an invalid provider, one not matching the filter,
                  // or one that isn't in the given package, if any.
                  boolean inPackage = packageName == null
                          || provider.id.componentName.getPackageName().equals(packageName);
                  if (provider.zombie || (info.widgetCategory & categoryFilter) == 0 || !inPackage) {
                      continue;
                  }
  
                  // Add providers only for the requested profile that are white-listed.
                  final int providerProfileId = info.getProfile().getIdentifier();
                  if (providerProfileId == profileId
                          && mSecurityPolicy.isProviderInCallerOrInProfileAndWhitelListed(
                              provider.id.componentName.getPackageName(), providerProfileId)) {
                      result.add(cloneIfLocalBinder(info));
                  }
              }
  
              return new ParceledListSlice<AppWidgetProviderInfo>(result);
          }
      }
  
      private void updateAppWidgetIds(String callingPackage, int[] appWidgetIds,
              RemoteViews views, boolean partially) {
          final int userId = UserHandle.getCallingUserId();
  
          if (appWidgetIds == null || appWidgetIds.length == 0) {
              return;
          }
  
          // Make sure the package runs under the caller uid.
          mSecurityPolicy.enforceCallFromPackage(callingPackage);
          synchronized (mLock) {
              ensureGroupStateLoadedLocked(userId);
  
              final int N = appWidgetIds.length;
              for (int i = 0; i < N; i++) {
                  final int appWidgetId = appWidgetIds[i];
  
                  // NOTE: The lookup is enforcing security across users by making
                  // sure the caller can only access widgets it hosts or provides.
                  Widget widget = lookupWidgetLocked(appWidgetId,
                          Binder.getCallingUid(), callingPackage);
  
                  if (widget != null) {
                      updateAppWidgetInstanceLocked(widget, views, partially);
                  }
              }
          }
      }
  
      private int incrementAndGetAppWidgetIdLocked(int userId) {
          final int appWidgetId = peekNextAppWidgetIdLocked(userId) + 1;
          mNextAppWidgetIds.put(userId, appWidgetId);
          return appWidgetId;
      }
  
      private void setMinAppWidgetIdLocked(int userId, int minWidgetId) {
          final int nextAppWidgetId = peekNextAppWidgetIdLocked(userId);
          if (nextAppWidgetId < minWidgetId) {
              mNextAppWidgetIds.put(userId, minWidgetId);
          }
      }
  
      private int peekNextAppWidgetIdLocked(int userId) {
          if (mNextAppWidgetIds.indexOfKey(userId) < 0) {
              return AppWidgetManager.INVALID_APPWIDGET_ID + 1;
          } else {
              return mNextAppWidgetIds.get(userId);
          }
      }
  
      private Host lookupOrAddHostLocked(HostId id) {
          Host host = lookupHostLocked(id);
          if (host != null) {
              return host;
          }
  
          host = new Host();
          host.id = id;
          mHosts.add(host);
  
          return host;
      }
  
      private void deleteHostLocked(Host host) {
          final int N = host.widgets.size();
          for (int i = N - 1; i >= 0; i--) {
              Widget widget = host.widgets.remove(i);
              deleteAppWidgetLocked(widget);
          }
          mHosts.remove(host);
  
          // it's gone or going away, abruptly drop the callback connection
          host.callbacks = null;
      }
  
      private void deleteAppWidgetLocked(Widget widget) {
          // We first unbind all services that are bound to this id
          // Check if we need to destroy any services (if no other app widgets are
          // referencing the same service)
          decrementAppWidgetServiceRefCount(widget);
  
          Host host = widget.host;
          host.widgets.remove(widget);
          pruneHostLocked(host);
  
          removeWidgetLocked(widget);
  
          Provider provider = widget.provider;
          if (provider != null) {
              provider.widgets.remove(widget);
              if (!provider.zombie) {
                  // send the broacast saying that this appWidgetId has been deleted
                  sendDeletedIntentLocked(widget);
  
                  if (provider.widgets.isEmpty()) {
                      // cancel the future updates
                      cancelBroadcastsLocked(provider);
  
                      // send the broacast saying that the provider is not in use any more
                      sendDisabledIntentLocked(provider);
                  }
              }
          }
      }
  
      private void cancelBroadcastsLocked(Provider provider) {
          if (DEBUG) {
              Slog.i(TAG, "cancelBroadcastsLocked() for " + provider);
          }
          if (provider.broadcast != null) {
              final PendingIntent broadcast = provider.broadcast;
              mSaveStateHandler.post(() -> {
                      mAlarmManager.cancel(broadcast);
                      broadcast.cancel();
              });
              provider.broadcast = null;
          }
      }
  
      // Destroys the cached factory on the RemoteViewsService's side related to the specified intent
      private void destroyRemoteViewsService(final Intent intent, Widget widget) {
          final ServiceConnection conn = new ServiceConnection() {
              @Override
              public void onServiceConnected(ComponentName name, IBinder service) {
                  final IRemoteViewsFactory cb = IRemoteViewsFactory.Stub.asInterface(service);
                  try {
                      cb.onDestroy(intent);
                  } catch (RemoteException re) {
                      Slog.e(TAG, "Error calling remove view factory", re);
                  }
                  mContext.unbindService(this);
              }
  
              @Override
              public void onServiceDisconnected(ComponentName name) {
                  // Do nothing
              }
          };
  
          // Bind to the service and remove the static intent->factory mapping in the
          // RemoteViewsService.
          final long token = Binder.clearCallingIdentity();
          try {
              mContext.bindServiceAsUser(intent, conn,
                      Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
                      widget.provider.info.getProfile());
          } finally {
              Binder.restoreCallingIdentity(token);
          }
      }
  
      // Adds to the ref-count for a given RemoteViewsService intent
      private void incrementAppWidgetServiceRefCount(int appWidgetId,
              Pair<Integer, FilterComparison> serviceId) {
          final HashSet<Integer> appWidgetIds;
          if (mRemoteViewsServicesAppWidgets.containsKey(serviceId)) {
              appWidgetIds = mRemoteViewsServicesAppWidgets.get(serviceId);
          } else {
              appWidgetIds = new HashSet<>();
              mRemoteViewsServicesAppWidgets.put(serviceId, appWidgetIds);
          }
          appWidgetIds.add(appWidgetId);
      }
  
      // Subtracts from the ref-count for a given RemoteViewsService intent, prompting a delete if
      // the ref-count reaches zero.
      private void decrementAppWidgetServiceRefCount(Widget widget) {
          Iterator<Pair<Integer, FilterComparison>> it = mRemoteViewsServicesAppWidgets
                  .keySet().iterator();
          while (it.hasNext()) {
              final Pair<Integer, FilterComparison> key = it.next();
              final HashSet<Integer> ids = mRemoteViewsServicesAppWidgets.get(key);
              if (ids.remove(widget.appWidgetId)) {
                  // If we have removed the last app widget referencing this service, then we
                  // should destroy it and remove it from this set
                  if (ids.isEmpty()) {
                      destroyRemoteViewsService(key.second.getIntent(), widget);
                      it.remove();
                  }
              }
          }
      }
  
      private void saveGroupStateAsync(int groupId) {
          mSaveStateHandler.post(new SaveStateRunnable(groupId));
      }
  
      private void updateAppWidgetInstanceLocked(Widget widget, RemoteViews views,
              boolean isPartialUpdate) {
          if (widget != null && widget.provider != null
                  && !widget.provider.zombie && !widget.host.zombie) {
  
              if (isPartialUpdate && widget.views != null) {
                  // For a partial update, we merge the new RemoteViews with the old.
                  widget.views.mergeRemoteViews(views);
              } else {
                  // For a full update we replace the RemoteViews completely.
                  widget.views = views;
              }
              int memoryUsage;
              if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) &&
                      (widget.views != null) &&
                      ((memoryUsage = widget.views.estimateMemoryUsage()) > mMaxWidgetBitmapMemory)) {
                  widget.views = null;
                  throw new IllegalArgumentException("RemoteViews for widget update exceeds"
                          + " maximum bitmap memory usage (used: " + memoryUsage
                          + ", max: " + mMaxWidgetBitmapMemory + ")");
              }
              scheduleNotifyUpdateAppWidgetLocked(widget, widget.getEffectiveViewsLocked());
          }
      }
  
      private void scheduleNotifyAppWidgetViewDataChanged(Widget widget, int viewId) {
          if (viewId == ID_VIEWS_UPDATE || viewId == ID_PROVIDER_CHANGED) {
              // A view id should never collide with these constants but a developer can call this
              // method with a wrong id. In that case, ignore the call.
              return;
          }
          long requestId = UPDATE_COUNTER.incrementAndGet();
          if (widget != null) {
              widget.updateSequenceNos.put(viewId, requestId);
          }
          if (widget == null || widget.host == null || widget.host.zombie
                  || widget.host.callbacks == null || widget.provider == null
                  || widget.provider.zombie) {
              return;
          }
  
          SomeArgs args = SomeArgs.obtain();
          args.arg1 = widget.host;
          args.arg2 = widget.host.callbacks;
          args.arg3 = requestId;
          args.argi1 = widget.appWidgetId;
          args.argi2 = viewId;
  
          mCallbackHandler.obtainMessage(
                  CallbackHandler.MSG_NOTIFY_VIEW_DATA_CHANGED,
                  args).sendToTarget();
      }
  
  
      private void handleNotifyAppWidgetViewDataChanged(Host host, IAppWidgetHost callbacks,
              int appWidgetId, int viewId, long requestId) {
          try {
              callbacks.viewDataChanged(appWidgetId, viewId);
              host.lastWidgetUpdateSequenceNo = requestId;
          } catch (RemoteException re) {
              // It failed; remove the callback. No need to prune because
              // we know that this host is still referenced by this instance.
              callbacks = null;
          }
  
          // If the host is unavailable, then we call the associated
          // RemoteViewsFactory.onDataSetChanged() directly
          synchronized (mLock) {
              if (callbacks == null) {
                  host.callbacks = null;
  
                  Set<Pair<Integer, FilterComparison>> keys = mRemoteViewsServicesAppWidgets.keySet();
                  for (Pair<Integer, FilterComparison> key : keys) {
                      if (mRemoteViewsServicesAppWidgets.get(key).contains(appWidgetId)) {
                          final ServiceConnection connection = new ServiceConnection() {
                              @Override
                              public void onServiceConnected(ComponentName name, IBinder service) {
                                  IRemoteViewsFactory cb = IRemoteViewsFactory.Stub
                                          .asInterface(service);
                                  try {
                                      cb.onDataSetChangedAsync();
                                  } catch (RemoteException e) {
                                      Slog.e(TAG, "Error calling onDataSetChangedAsync()", e);
                                  }
                                  mContext.unbindService(this);
                              }
  
                              @Override
                              public void onServiceDisconnected(android.content.ComponentName name) {
                                  // Do nothing
                              }
                          };
  
                          final int userId = UserHandle.getUserId(key.first);
                          Intent intent = key.second.getIntent();
  
                          // Bind to the service and call onDataSetChanged()
                          bindService(intent, connection, new UserHandle(userId));
                      }
                  }
              }
          }
      }
  
      private void scheduleNotifyUpdateAppWidgetLocked(Widget widget, RemoteViews updateViews) {
          long requestId = UPDATE_COUNTER.incrementAndGet();
          if (widget != null) {
              widget.updateSequenceNos.put(ID_VIEWS_UPDATE, requestId);
          }
          if (widget == null || widget.provider == null || widget.provider.zombie
                  || widget.host.callbacks == null || widget.host.zombie) {
              return;
          }
  
          SomeArgs args = SomeArgs.obtain();
          args.arg1 = widget.host;
          args.arg2 = widget.host.callbacks;
          args.arg3 = (updateViews != null) ? updateViews.clone() : null;
          args.arg4 = requestId;
          args.argi1 = widget.appWidgetId;
  
          mCallbackHandler.obtainMessage(
                  CallbackHandler.MSG_NOTIFY_UPDATE_APP_WIDGET,
                  args).sendToTarget();
      }
  
      private void handleNotifyUpdateAppWidget(Host host, IAppWidgetHost callbacks,
              int appWidgetId, RemoteViews views, long requestId) {
          try {
              callbacks.updateAppWidget(appWidgetId, views);
              host.lastWidgetUpdateSequenceNo = requestId;
          } catch (RemoteException re) {
              synchronized (mLock) {
                  Slog.e(TAG, "Widget host dead: " + host.id, re);
                  host.callbacks = null;
              }
          }
      }
  
      private void scheduleNotifyProviderChangedLocked(Widget widget) {
          long requestId = UPDATE_COUNTER.incrementAndGet();
          if (widget != null) {
              // When the provider changes, reset everything else.
              widget.updateSequenceNos.clear();
              widget.updateSequenceNos.append(ID_PROVIDER_CHANGED, requestId);
          }
          if (widget == null || widget.provider == null || widget.provider.zombie
                  || widget.host.callbacks == null || widget.host.zombie) {
              return;
          }
  
          SomeArgs args = SomeArgs.obtain();
          args.arg1 = widget.host;
          args.arg2 = widget.host.callbacks;
          args.arg3 = widget.provider.info;
          args.arg4 = requestId;
          args.argi1 = widget.appWidgetId;
  
          mCallbackHandler.obtainMessage(
                  CallbackHandler.MSG_NOTIFY_PROVIDER_CHANGED,
                  args).sendToTarget();
      }
  
      private void handleNotifyProviderChanged(Host host, IAppWidgetHost callbacks,
              int appWidgetId, AppWidgetProviderInfo info, long requestId) {
          try {
              callbacks.providerChanged(appWidgetId, info);
              host.lastWidgetUpdateSequenceNo = requestId;
          } catch (RemoteException re) {
              synchronized (mLock){
                  Slog.e(TAG, "Widget host dead: " + host.id, re);
                  host.callbacks = null;
              }
          }
      }
  
      private void scheduleNotifyGroupHostsForProvidersChangedLocked(int userId) {
          final int[] profileIds = mSecurityPolicy.getEnabledGroupProfileIds(userId);
  
          final int N = mHosts.size();
          for (int i = N - 1; i >= 0; i--) {
              Host host = mHosts.get(i);
  
              boolean hostInGroup = false;
              final int M = profileIds.length;
              for (int j = 0; j < M; j++) {
                  final int profileId = profileIds[j];
                  if (host.getUserId() == profileId) {
                      hostInGroup = true;
                      break;
                  }
              }
  
              if (!hostInGroup) {
                  continue;
              }
  
              if (host == null || host.zombie || host.callbacks == null) {
                  continue;
              }
  
              SomeArgs args = SomeArgs.obtain();
              args.arg1 = host;
              args.arg2 = host.callbacks;
  
              mCallbackHandler.obtainMessage(
                      CallbackHandler.MSG_NOTIFY_PROVIDERS_CHANGED,
                      args).sendToTarget();
          }
      }
  
      private void handleNotifyProvidersChanged(Host host, IAppWidgetHost callbacks) {
          try {
              callbacks.providersChanged();
          } catch (RemoteException re) {
              synchronized (mLock) {
                  Slog.e(TAG, "Widget host dead: " + host.id, re);
                  host.callbacks = null;
              }
          }
      }
  
      private static boolean isLocalBinder() {
          return Process.myPid() == Binder.getCallingPid();
      }
  
      private static RemoteViews cloneIfLocalBinder(RemoteViews rv) {
          if (isLocalBinder() && rv != null) {
              return rv.clone();
          }
          return rv;
      }
  
      private static AppWidgetProviderInfo cloneIfLocalBinder(AppWidgetProviderInfo info) {
          if (isLocalBinder() && info != null) {
              return info.clone();
          }
          return info;
      }
  
      private static Bundle cloneIfLocalBinder(Bundle bundle) {
          // Note: this is only a shallow copy. For now this will be fine, but it could be problematic
          // if we start adding objects to the options. Further, it would only be an issue if keyguard
          // used such options.
          if (isLocalBinder() && bundle != null) {
              return (Bundle) bundle.clone();
          }
          return bundle;
      }
  
      private Widget lookupWidgetLocked(int appWidgetId, int uid, String packageName) {
          final int N = mWidgets.size();
          for (int i = 0; i < N; i++) {
              Widget widget = mWidgets.get(i);
              if (widget.appWidgetId == appWidgetId
                      && mSecurityPolicy.canAccessAppWidget(widget, uid, packageName)) {
                  return widget;
              }
          }
          return null;
      }
  
      private Provider lookupProviderLocked(ProviderId id) {
          final int N = mProviders.size();
          for (int i = 0; i < N; i++) {
              Provider provider = mProviders.get(i);
              if (provider.id.equals(id)) {
                  return provider;
              }
          }
          return null;
      }
  
      private Host lookupHostLocked(HostId hostId) {
          final int N = mHosts.size();
          for (int i = 0; i < N; i++) {
              Host host = mHosts.get(i);
              if (host.id.equals(hostId)) {
                  return host;
              }
          }
          return null;
      }
  
      private void pruneHostLocked(Host host) {
          if (host.widgets.size() == 0 && host.callbacks == null) {
              if (DEBUG) {
                  Slog.i(TAG, "Pruning host " + host.id);
              }
              mHosts.remove(host);
          }
      }
  
      private void loadGroupWidgetProvidersLocked(int[] profileIds) {
          List<ResolveInfo> allReceivers = null;
          Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
  
          final int profileCount = profileIds.length;
          for (int i = 0; i < profileCount; i++) {
              final int profileId = profileIds[i];
  
              List<ResolveInfo> receivers = queryIntentReceivers(intent, profileId);
              if (receivers != null && !receivers.isEmpty()) {
                  if (allReceivers == null) {
                      allReceivers = new ArrayList<>();
                  }
                  allReceivers.addAll(receivers);
              }
          }
  
          final int N = (allReceivers == null) ? 0 : allReceivers.size();
          for (int i = 0; i < N; i++) {
              ResolveInfo receiver = allReceivers.get(i);
              addProviderLocked(receiver);
          }
      }
  
      private boolean addProviderLocked(ResolveInfo ri) {
          if ((ri.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
              return false;
          }
  
          if (!ri.activityInfo.isEnabled()) {
              return false;
          }
  
          ComponentName componentName = new ComponentName(ri.activityInfo.packageName,
                  ri.activityInfo.name);
          ProviderId providerId = new ProviderId(ri.activityInfo.applicationInfo.uid, componentName);
  
          Provider provider = parseProviderInfoXml(providerId, ri, null);
          if (provider != null) {
              // we might have an inactive entry for this provider already due to
              // a preceding restore operation.  if so, fix it up in place; otherwise
              // just add this new one.
              Provider existing = lookupProviderLocked(providerId);
  
              // If the provider was not found it may be because it was restored and
              // we did not know its UID so let us find if there is such one.
              if (existing == null) {
                  ProviderId restoredProviderId = new ProviderId(UNKNOWN_UID, componentName);
                  existing = lookupProviderLocked(restoredProviderId);
              }
  
              if (existing != null) {
                  if (existing.zombie && !mSafeMode) {
                      // it's a placeholder that was set up during an app restore
                      existing.id = providerId;
                      existing.zombie = false;
                      existing.info = provider.info; // the real one filled out from the ResolveInfo
                      if (DEBUG) {
                          Slog.i(TAG, "Provider placeholder now reified: " + existing);
                      }
                  }
              } else {
                  mProviders.add(provider);
              }
              return true;
          }
  
          return false;
      }
  
      // Remove widgets for provider that are hosted in userId.
      private void deleteWidgetsLocked(Provider provider, int userId) {
          final int N = provider.widgets.size();
          for (int i = N - 1; i >= 0; i--) {
              Widget widget = provider.widgets.get(i);
              if (userId == UserHandle.USER_ALL
                      || userId == widget.host.getUserId()) {
                  provider.widgets.remove(i);
                  // Call back with empty RemoteViews
                  updateAppWidgetInstanceLocked(widget, null, false);
                  // clear out references to this appWidgetId
                  widget.host.widgets.remove(widget);
                  removeWidgetLocked(widget);
                  widget.provider = null;
                  pruneHostLocked(widget.host);
                  widget.host = null;
              }
          }
      }
  
      private void deleteProviderLocked(Provider provider) {
          deleteWidgetsLocked(provider, UserHandle.USER_ALL);
          mProviders.remove(provider);
  
          // no need to send the DISABLE broadcast, since the receiver is gone anyway
          cancelBroadcastsLocked(provider);
      }
  
      private void sendEnableIntentLocked(Provider p) {
          Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED);
          intent.setComponent(p.info.provider);
          sendBroadcastAsUser(intent, p.info.getProfile());
      }
  
      private void sendUpdateIntentLocked(Provider provider, int[] appWidgetIds) {
          Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
          intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
          intent.setComponent(provider.info.provider);
          sendBroadcastAsUser(intent, provider.info.getProfile());
      }
  
      private void sendDeletedIntentLocked(Widget widget) {
          Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DELETED);
          intent.setComponent(widget.provider.info.provider);
          intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widget.appWidgetId);
          sendBroadcastAsUser(intent, widget.provider.info.getProfile());
      }
  
      private void sendDisabledIntentLocked(Provider provider) {
          Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_DISABLED);
          intent.setComponent(provider.info.provider);
          sendBroadcastAsUser(intent, provider.info.getProfile());
      }
  
      public void sendOptionsChangedIntentLocked(Widget widget) {
          Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED);
          intent.setComponent(widget.provider.info.provider);
          intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widget.appWidgetId);
          intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, widget.options);
          sendBroadcastAsUser(intent, widget.provider.info.getProfile());
      }
  
      private void registerForBroadcastsLocked(Provider provider, int[] appWidgetIds) {
          if (provider.info.updatePeriodMillis > 0) {
              // if this is the first instance, set the alarm. otherwise,
              // rely on the fact that we've already set it and that
              // PendingIntent.getBroadcast will update the extras.
              boolean alreadyRegistered = provider.broadcast != null;
              Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
              intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
              intent.setComponent(provider.info.provider);
              long token = Binder.clearCallingIdentity();
              try {
                  provider.broadcast = PendingIntent.getBroadcastAsUser(mContext, 1, intent,
                          PendingIntent.FLAG_UPDATE_CURRENT, provider.info.getProfile());
              } finally {
                  Binder.restoreCallingIdentity(token);
              }
              if (!alreadyRegistered) {
                  // Set the alarm outside of our locks; we've latched the first-time
                  // invariant and established the PendingIntent safely.
                  final long period = Math.max(provider.info.updatePeriodMillis, MIN_UPDATE_PERIOD);
                  final PendingIntent broadcast = provider.broadcast;
                  mSaveStateHandler.post(() ->
                      mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                              SystemClock.elapsedRealtime() + period, period, broadcast)
                  );
              }
          }
      }
  
      private static int[] getWidgetIds(ArrayList<Widget> widgets) {
          int instancesSize = widgets.size();
          int appWidgetIds[] = new int[instancesSize];
          for (int i = 0; i < instancesSize; i++) {
              appWidgetIds[i] = widgets.get(i).appWidgetId;
          }
          return appWidgetIds;
      }
  
      private static void dumpProvider(Provider provider, int index, PrintWriter pw) {
          AppWidgetProviderInfo info = provider.info;
          pw.print("  ["); pw.print(index); pw.print("] provider ");
          pw.println(provider.id);
          pw.print("    min=("); pw.print(info.minWidth);
          pw.print("x"); pw.print(info.minHeight);
          pw.print(")   minResize=("); pw.print(info.minResizeWidth);
          pw.print("x"); pw.print(info.minResizeHeight);
          pw.print(") updatePeriodMillis=");
          pw.print(info.updatePeriodMillis);
          pw.print(" resizeMode=");
          pw.print(info.resizeMode);
          pw.print(" widgetCategory=");
          pw.print(info.widgetCategory);
          pw.print(" autoAdvanceViewId=");
          pw.print(info.autoAdvanceViewId);
          pw.print(" initialLayout=#");
          pw.print(Integer.toHexString(info.initialLayout));
          pw.print(" initialKeyguardLayout=#");
          pw.print(Integer.toHexString(info.initialKeyguardLayout));
          pw.print(" zombie="); pw.println(provider.zombie);
      }
  
      private static void dumpHost(Host host, int index, PrintWriter pw) {
          pw.print("  ["); pw.print(index); pw.print("] hostId=");
          pw.println(host.id);
          pw.print("    callbacks="); pw.println(host.callbacks);
          pw.print("    widgets.size="); pw.print(host.widgets.size());
          pw.print(" zombie="); pw.println(host.zombie);
      }
  
      private static void dumpGrant(Pair<Integer, String> grant, int index, PrintWriter pw) {
          pw.print("  ["); pw.print(index); pw.print(']');
          pw.print(" user="); pw.print(grant.first);
          pw.print(" package="); pw.println(grant.second);
      }
  
      private static void dumpWidget(Widget widget, int index, PrintWriter pw) {
          pw.print("  ["); pw.print(index); pw.print("] id=");
          pw.println(widget.appWidgetId);
          pw.print("    host=");
          pw.println(widget.host.id);
          if (widget.provider != null) {
              pw.print("    provider="); pw.println(widget.provider.id);
          }
          if (widget.host != null) {
              pw.print("    host.callbacks="); pw.println(widget.host.callbacks);
          }
          if (widget.views != null) {
              pw.print("    views="); pw.println(widget.views);
          }
      }
  
      private static void serializeProvider(XmlSerializer out, Provider p) throws IOException {
          out.startTag(null, "p");
          out.attribute(null, "pkg", p.info.provider.getPackageName());
          out.attribute(null, "cl", p.info.provider.getClassName());
          out.attribute(null, "tag", Integer.toHexString(p.tag));
          if (!TextUtils.isEmpty(p.infoTag)) {
              out.attribute(null, "info_tag", p.infoTag);
          }
          out.endTag(null, "p");
      }
  
      private static void serializeHost(XmlSerializer out, Host host) throws IOException {
          out.startTag(null, "h");
          out.attribute(null, "pkg", host.id.packageName);
          out.attribute(null, "id", Integer.toHexString(host.id.hostId));
          out.attribute(null, "tag", Integer.toHexString(host.tag));
          out.endTag(null, "h");
      }
  
      private static void serializeAppWidget(XmlSerializer out, Widget widget) throws IOException {
          out.startTag(null, "g");
          out.attribute(null, "id", Integer.toHexString(widget.appWidgetId));
          out.attribute(null, "rid", Integer.toHexString(widget.restoredId));
          out.attribute(null, "h", Integer.toHexString(widget.host.tag));
          if (widget.provider != null) {
              out.attribute(null, "p", Integer.toHexString(widget.provider.tag));
          }
          if (widget.options != null) {
              int minWidth = widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH);
              int minHeight = widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT);
              int maxWidth = widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH);
              int maxHeight = widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT);
              out.attribute(null, "min_width", Integer.toHexString((minWidth > 0) ? minWidth : 0));
              out.attribute(null, "min_height", Integer.toHexString((minHeight > 0) ? minHeight : 0));
              out.attribute(null, "max_width", Integer.toHexString((maxWidth > 0) ? maxWidth : 0));
              out.attribute(null, "max_height", Integer.toHexString((maxHeight > 0) ? maxHeight : 0));
              out.attribute(null, "host_category", Integer.toHexString(widget.options.getInt(
                      AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)));
          }
          out.endTag(null, "g");
      }
  
      @Override
      public List<String> getWidgetParticipants(int userId) {
          return mBackupRestoreController.getWidgetParticipants(userId);
      }
  
      @Override
      public byte[] getWidgetState(String packageName, int userId) {
          return mBackupRestoreController.getWidgetState(packageName, userId);
      }
  
      @Override
      public void restoreStarting(int userId) {
          mBackupRestoreController.restoreStarting(userId);
      }
  
      @Override
      public void restoreWidgetState(String packageName, byte[] restoredState, int userId) {
          mBackupRestoreController.restoreWidgetState(packageName, restoredState, userId);
      }
  
      @Override
      public void restoreFinished(int userId) {
          mBackupRestoreController.restoreFinished(userId);
      }
  
      @SuppressWarnings("deprecation")
      private Provider parseProviderInfoXml(ProviderId providerId, ResolveInfo ri,
              Provider oldProvider) {
          AppWidgetProviderInfo info = null;
          if (oldProvider != null && !TextUtils.isEmpty(oldProvider.infoTag)) {
              info = parseAppWidgetProviderInfo(providerId, ri.activityInfo, oldProvider.infoTag);
          }
          if (info == null) {
              info = parseAppWidgetProviderInfo(providerId, ri.activityInfo,
                      AppWidgetManager.META_DATA_APPWIDGET_PROVIDER);
          }
          if (info == null) {
              return null;
          }
  
          Provider provider = new Provider();
          provider.id = providerId;
          provider.info = info;
          return provider;
      }
  
      private AppWidgetProviderInfo parseAppWidgetProviderInfo(
              ProviderId providerId, ActivityInfo activityInfo, String metadataKey) {
          try (XmlResourceParser parser =
                       activityInfo.loadXmlMetaData(mContext.getPackageManager(), metadataKey)) {
              if (parser == null) {
                  Slog.w(TAG, "No " + metadataKey + " meta-data for AppWidget provider '"
                          + providerId + '\'');
                  return null;
              }
  
              AttributeSet attrs = Xml.asAttributeSet(parser);
  
              int type;
              while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                      && type != XmlPullParser.START_TAG) {
                  // drain whitespace, comments, etc.
              }
  
              String nodeName = parser.getName();
              if (!"appwidget-provider".equals(nodeName)) {
                  Slog.w(TAG, "Meta-data does not start with appwidget-provider tag for"
                          + " AppWidget provider " + providerId.componentName
                          + " for user " + providerId.uid);
                  return null;
              }
  
              AppWidgetProviderInfo info = new AppWidgetProviderInfo();
              info.provider = providerId.componentName;
              info.providerInfo = activityInfo;
  
              final Resources resources;
              final long identity = Binder.clearCallingIdentity();
              try {
                  final PackageManager pm = mContext.getPackageManager();
                  final int userId = UserHandle.getUserId(providerId.uid);
                  final ApplicationInfo app = pm.getApplicationInfoAsUser(activityInfo.packageName,
                          0, userId);
                  resources = pm.getResourcesForApplication(app);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
  
              TypedArray sa = resources.obtainAttributes(attrs,
                      com.android.internal.R.styleable.AppWidgetProviderInfo);
  
              // These dimensions has to be resolved in the application's context.
              // We simply send back the raw complex data, which will be
              // converted to dp in {@link AppWidgetManager#getAppWidgetInfo}.
              TypedValue value = sa
                      .peekValue(com.android.internal.R.styleable.AppWidgetProviderInfo_minWidth);
              info.minWidth = value != null ? value.data : 0;
              value = sa.peekValue(com.android.internal.R.styleable.AppWidgetProviderInfo_minHeight);
              info.minHeight = value != null ? value.data : 0;
              value = sa.peekValue(
                      com.android.internal.R.styleable.AppWidgetProviderInfo_minResizeWidth);
              info.minResizeWidth = value != null ? value.data : info.minWidth;
              value = sa.peekValue(
                      com.android.internal.R.styleable.AppWidgetProviderInfo_minResizeHeight);
              info.minResizeHeight = value != null ? value.data : info.minHeight;
              info.updatePeriodMillis = sa.getInt(
                      com.android.internal.R.styleable.AppWidgetProviderInfo_updatePeriodMillis, 0);
              info.initialLayout = sa.getResourceId(
                      com.android.internal.R.styleable.AppWidgetProviderInfo_initialLayout, 0);
              info.initialKeyguardLayout = sa.getResourceId(com.android.internal.R.styleable.
                      AppWidgetProviderInfo_initialKeyguardLayout, 0);
  
              String className = sa
                      .getString(com.android.internal.R.styleable.AppWidgetProviderInfo_configure);
              if (className != null) {
                  info.configure = new ComponentName(providerId.componentName.getPackageName(),
                          className);
              }
              info.label = activityInfo.loadLabel(mContext.getPackageManager()).toString();
              info.icon = activityInfo.getIconResource();
              info.previewImage = sa.getResourceId(
                      com.android.internal.R.styleable.AppWidgetProviderInfo_previewImage, 0);
              info.autoAdvanceViewId = sa.getResourceId(
                      com.android.internal.R.styleable.AppWidgetProviderInfo_autoAdvanceViewId, -1);
              info.resizeMode = sa.getInt(
                      com.android.internal.R.styleable.AppWidgetProviderInfo_resizeMode,
                      AppWidgetProviderInfo.RESIZE_NONE);
              info.widgetCategory = sa.getInt(
                      com.android.internal.R.styleable.AppWidgetProviderInfo_widgetCategory,
                      AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN);
              info.widgetFeatures = sa.getInt(
                      com.android.internal.R.styleable.AppWidgetProviderInfo_widgetFeatures, 0);
  
              sa.recycle();
              return info;
          } catch (IOException | PackageManager.NameNotFoundException | XmlPullParserException e) {
              // Ok to catch Exception here, because anything going wrong because
              // of what a client process passes to us should not be fatal for the
              // system process.
              Slog.w(TAG, "XML parsing failed for AppWidget provider "
                      + providerId.componentName + " for user " + providerId.uid, e);
              return null;
          }
      }
  
      private int getUidForPackage(String packageName, int userId) {
          PackageInfo pkgInfo = null;
  
          final long identity = Binder.clearCallingIdentity();
          try {
              pkgInfo = mPackageManager.getPackageInfo(packageName, 0, userId);
          } catch (RemoteException re) {
              // Shouldn't happen, local call
          } finally {
              Binder.restoreCallingIdentity(identity);
          }
  
          if (pkgInfo == null || pkgInfo.applicationInfo == null) {
              return -1;
          }
  
          return pkgInfo.applicationInfo.uid;
      }
  
      private ActivityInfo getProviderInfo(ComponentName componentName, int userId) {
          Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
          intent.setComponent(componentName);
  
          List<ResolveInfo> receivers = queryIntentReceivers(intent, userId);
          // We are setting component, so there is only one or none.
          if (!receivers.isEmpty()) {
              return receivers.get(0).activityInfo;
          }
  
          return null;
      }
  
      private List<ResolveInfo> queryIntentReceivers(Intent intent, int userId) {
          final long identity = Binder.clearCallingIdentity();
          try {
              int flags = PackageManager.GET_META_DATA;
  
              // We really need packages to be around and parsed to know if they
              // provide widgets.
              flags |= PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
  
              // Widget hosts that are non-crypto aware may be hosting widgets
              // from a profile that is still locked, so let them see those
              // widgets.
              if (isProfileWithUnlockedParent(userId)) {
                  flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE
                          | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
              }
  
              // Widgets referencing shared libraries need to have their
              // dependencies loaded.
              flags |= PackageManager.GET_SHARED_LIBRARY_FILES;
  
              return mPackageManager.queryIntentReceivers(intent,
                      intent.resolveTypeIfNeeded(mContext.getContentResolver()),
                      flags, userId).getList();
          } catch (RemoteException re) {
              return Collections.emptyList();
          } finally {
              Binder.restoreCallingIdentity(identity);
          }
      }
  
      /**
       * This does not use the usual onUserUnlocked() listener mechanism because it is
       * invoked at a choreographed point in the middle of the user unlock sequence,
       * before the boot-completed broadcast is issued and the listeners notified.
       */
      void handleUserUnlocked(int userId) {
          if (isProfileWithLockedParent(userId)) {
              return;
          }
          if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
              Slog.w(TAG, "User " + userId + " is no longer unlocked - exiting");
              return;
          }
          long time = SystemClock.elapsedRealtime();
          synchronized (mLock) {
              Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "appwidget ensure");
              ensureGroupStateLoadedLocked(userId);
              Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
              Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "appwidget reload");
              reloadWidgetsMaskedStateForGroup(mSecurityPolicy.getGroupParent(userId));
              Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
  
              final int N = mProviders.size();
              for (int i = 0; i < N; i++) {
                  Provider provider = mProviders.get(i);
  
                  // Send broadcast only to the providers of the user.
                  if (provider.getUserId() != userId) {
                      continue;
                  }
  
                  if (provider.widgets.size() > 0) {
                      Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                              "appwidget init " + provider.info.provider.getPackageName());
                      sendEnableIntentLocked(provider);
                      int[] appWidgetIds = getWidgetIds(provider.widgets);
                      sendUpdateIntentLocked(provider, appWidgetIds);
                      registerForBroadcastsLocked(provider, appWidgetIds);
                      Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                  }
              }
          }
          Slog.i(TAG, "Processing of handleUserUnlocked u" + userId + " took "
                  + (SystemClock.elapsedRealtime() - time) + " ms");
      }
  
      // only call from initialization -- it assumes that the data structures are all empty
      private void loadGroupStateLocked(int[] profileIds) {
          // We can bind the widgets to host and providers only after
          // reading the host and providers for all users since a widget
          // can have a host and a provider in different users.
          List<LoadedWidgetState> loadedWidgets = new ArrayList<>();
  
          int version = 0;
  
          final int profileIdCount = profileIds.length;
          for (int i = 0; i < profileIdCount; i++) {
              final int profileId = profileIds[i];
  
              // No file written for this user - nothing to do.
              AtomicFile file = getSavedStateFile(profileId);
              try (FileInputStream stream = file.openRead()) {
                  version = readProfileStateFromFileLocked(stream, profileId, loadedWidgets);
              } catch (IOException e) {
                  Slog.w(TAG, "Failed to read state: " + e);
              }
          }
  
          if (version >= 0) {
              // Hooke'm up...
              bindLoadedWidgetsLocked(loadedWidgets);
  
              // upgrade the database if needed
              performUpgradeLocked(version);
          } else {
              // failed reading, clean up
              Slog.w(TAG, "Failed to read state, clearing widgets and hosts.");
              clearWidgetsLocked();
              mHosts.clear();
              final int N = mProviders.size();
              for (int i = 0; i < N; i++) {
                  mProviders.get(i).widgets.clear();
              }
          }
      }
  
      private void bindLoadedWidgetsLocked(List<LoadedWidgetState> loadedWidgets) {
          final int loadedWidgetCount = loadedWidgets.size();
          for (int i = loadedWidgetCount - 1; i >= 0; i--) {
              LoadedWidgetState loadedWidget = loadedWidgets.remove(i);
              Widget widget = loadedWidget.widget;
  
              widget.provider = findProviderByTag(loadedWidget.providerTag);
              if (widget.provider == null) {
                  // This provider is gone. We just let the host figure out
                  // that this happened when it fails to load it.
                  continue;
              }
  
              widget.host = findHostByTag(loadedWidget.hostTag);
              if (widget.host == null) {
                  // This host is gone.
                  continue;
              }
  
              widget.provider.widgets.add(widget);
              widget.host.widgets.add(widget);
              addWidgetLocked(widget);
          }
      }
  
      private Provider findProviderByTag(int tag) {
          if (tag < 0) {
              return null;
          }
          final int providerCount = mProviders.size();
          for (int i = 0; i < providerCount; i++) {
              Provider provider = mProviders.get(i);
              if (provider.tag == tag) {
                  return provider;
              }
          }
          return null;
      }
  
      private Host findHostByTag(int tag) {
          if (tag < 0) {
              return null;
          }
          final int hostCount = mHosts.size();
          for (int i = 0; i < hostCount; i++) {
              Host host = mHosts.get(i);
              if (host.tag == tag) {
                  return host;
              }
          }
          return null;
      }
  
      /**
       * Adds the widget to mWidgets and tracks the package name in mWidgetPackages.
       */
      void addWidgetLocked(Widget widget) {
          mWidgets.add(widget);
  
          onWidgetProviderAddedOrChangedLocked(widget);
      }
  
      /**
       * Checks if the provider is assigned and updates the mWidgetPackages to track packages
       * that have bound widgets.
       */
      void onWidgetProviderAddedOrChangedLocked(Widget widget) {
          if (widget.provider == null) return;
  
          int userId = widget.provider.getUserId();
          ArraySet<String> packages = mWidgetPackages.get(userId);
          if (packages == null) {
              mWidgetPackages.put(userId, packages = new ArraySet<String>());
          }
          packages.add(widget.provider.info.provider.getPackageName());
  
          // If we are adding a widget it might be for a provider that
          // is currently masked, if so mask the widget.
          if (widget.provider.isMaskedLocked()) {
              maskWidgetsViewsLocked(widget.provider, widget);
          } else {
              widget.clearMaskedViewsLocked();
          }
      }
  
      /**
       * Removes a widget from mWidgets and updates the cache of bound widget provider packages.
       * If there are other widgets with the same package, leaves it in the cache, otherwise it
       * removes the associated package from the cache.
       */
      void removeWidgetLocked(Widget widget) {
          mWidgets.remove(widget);
  
          onWidgetRemovedLocked(widget);
      }
  
      private void onWidgetRemovedLocked(Widget widget) {
          if (widget.provider == null) return;
  
          final int userId = widget.provider.getUserId();
          final String packageName = widget.provider.info.provider.getPackageName();
          ArraySet<String> packages = mWidgetPackages.get(userId);
          if (packages == null) {
              return;
          }
          // Check if there is any other widget with the same package name.
          // Remove packageName if none.
          final int N = mWidgets.size();
          for (int i = 0; i < N; i++) {
              Widget w = mWidgets.get(i);
              if (w.provider == null) continue;
              if (w.provider.getUserId() == userId
                      && packageName.equals(w.provider.info.provider.getPackageName())) {
                  return;
              }
          }
          packages.remove(packageName);
      }
  
      /**
       * Clears all widgets and associated cache of packages with bound widgets.
       */
      void clearWidgetsLocked() {
          mWidgets.clear();
  
          onWidgetsClearedLocked();
      }
  
      private void onWidgetsClearedLocked() {
          mWidgetPackages.clear();
      }
  
      @Override
      public boolean isBoundWidgetPackage(String packageName, int userId) {
          if (Binder.getCallingUid() != Process.SYSTEM_UID) {
              throw new SecurityException("Only the system process can call this");
          }
          synchronized (mLock) {
              final ArraySet<String> packages = mWidgetPackages.get(userId);
              if (packages != null) {
                  return packages.contains(packageName);
              }
          }
          return false;
      }
  
      private void saveStateLocked(int userId) {
          tagProvidersAndHosts();
  
          final int[] profileIds = mSecurityPolicy.getEnabledGroupProfileIds(userId);
  
          final int profileCount = profileIds.length;
          for (int i = 0; i < profileCount; i++) {
              final int profileId = profileIds[i];
  
              AtomicFile file = getSavedStateFile(profileId);
              FileOutputStream stream;
              try {
                  stream = file.startWrite();
                  if (writeProfileStateToFileLocked(stream, profileId)) {
                      file.finishWrite(stream);
                  } else {
                      file.failWrite(stream);
                      Slog.w(TAG, "Failed to save state, restoring backup.");
                  }
              } catch (IOException e) {
                  Slog.w(TAG, "Failed open state file for write: " + e);
              }
          }
      }
  
      private void tagProvidersAndHosts() {
          final int providerCount = mProviders.size();
          for (int i = 0; i < providerCount; i++) {
              Provider provider = mProviders.get(i);
              provider.tag = i;
          }
  
          final int hostCount = mHosts.size();
          for (int i = 0; i < hostCount; i++) {
              Host host = mHosts.get(i);
              host.tag = i;
          }
      }
  
      private void clearProvidersAndHostsTagsLocked() {
          final int providerCount = mProviders.size();
          for (int i = 0; i < providerCount; i++) {
              Provider provider = mProviders.get(i);
              provider.tag = TAG_UNDEFINED;
          }
  
          final int hostCount = mHosts.size();
          for (int i = 0; i < hostCount; i++) {
              Host host = mHosts.get(i);
              host.tag = TAG_UNDEFINED;
          }
      }
  
      private boolean writeProfileStateToFileLocked(FileOutputStream stream, int userId) {
          int N;
  
          try {
              XmlSerializer out = new FastXmlSerializer();
              out.setOutput(stream, StandardCharsets.UTF_8.name());
              out.startDocument(null, true);
              out.startTag(null, "gs");
              out.attribute(null, "version", String.valueOf(CURRENT_VERSION));
  
              N = mProviders.size();
              for (int i = 0; i < N; i++) {
                  Provider provider = mProviders.get(i);
                  // Save only providers for the user.
                  if (provider.getUserId() != userId) {
                      continue;
                  }
                  if (provider.shouldBePersisted()) {
                      serializeProvider(out, provider);
                  }
              }
  
              N = mHosts.size();
              for (int i = 0; i < N; i++) {
                  Host host = mHosts.get(i);
                  // Save only hosts for the user.
                  if (host.getUserId() != userId) {
                      continue;
                  }
                  serializeHost(out, host);
              }
  
              N = mWidgets.size();
              for (int i = 0; i < N; i++) {
                  Widget widget = mWidgets.get(i);
                  // Save only widgets hosted by the user.
                  if (widget.host.getUserId() != userId) {
                      continue;
                  }
                  serializeAppWidget(out, widget);
              }
  
              Iterator<Pair<Integer, String>> it = mPackagesWithBindWidgetPermission.iterator();
              while (it.hasNext()) {
                  Pair<Integer, String> binding = it.next();
                  // Save only white listings for the user.
                  if (binding.first != userId) {
                      continue;
                  }
                  out.startTag(null, "b");
                  out.attribute(null, "packageName", binding.second);
                  out.endTag(null, "b");
              }
  
              out.endTag(null, "gs");
              out.endDocument();
              return true;
          } catch (IOException e) {
              Slog.w(TAG, "Failed to write state: " + e);
              return false;
          }
      }
  
      private int readProfileStateFromFileLocked(FileInputStream stream, int userId,
              List<LoadedWidgetState> outLoadedWidgets) {
          int version = -1;
          try {
              XmlPullParser parser = Xml.newPullParser();
              parser.setInput(stream, StandardCharsets.UTF_8.name());
  
              int legacyProviderIndex = -1;
              int legacyHostIndex = -1;
              int type;
              do {
                  type = parser.next();
                  if (type == XmlPullParser.START_TAG) {
                      String tag = parser.getName();
                      if ("gs".equals(tag)) {
                          String attributeValue = parser.getAttributeValue(null, "version");
                          try {
                              version = Integer.parseInt(attributeValue);
                          } catch (NumberFormatException e) {
                              version = 0;
                          }
                      } else if ("p".equals(tag)) {
                          legacyProviderIndex++;
                          // TODO: do we need to check that this package has the same signature
                          // as before?
                          String pkg = parser.getAttributeValue(null, "pkg");
                          String cl = parser.getAttributeValue(null, "cl");
  
                          pkg = getCanonicalPackageName(pkg, cl, userId);
                          if (pkg == null) {
                              continue;
                          }
  
                          final int uid = getUidForPackage(pkg, userId);
                          if (uid < 0) {
                              continue;
                          }
  
                          ComponentName componentName = new ComponentName(pkg, cl);
  
                          ActivityInfo providerInfo = getProviderInfo(componentName, userId);
                          if (providerInfo == null) {
                              continue;
                          }
  
                          ProviderId providerId = new ProviderId(uid, componentName);
                          Provider provider = lookupProviderLocked(providerId);
  
                          if (provider == null && mSafeMode) {
                              // if we're in safe mode, make a temporary one
                              provider = new Provider();
                              provider.info = new AppWidgetProviderInfo();
                              provider.info.provider = providerId.componentName;
                              provider.info.providerInfo = providerInfo;
                              provider.zombie = true;
                              provider.id = providerId;
                              mProviders.add(provider);
                          }
  
                          String tagAttribute = parser.getAttributeValue(null, "tag");
                          final int providerTag = !TextUtils.isEmpty(tagAttribute)
                                  ? Integer.parseInt(tagAttribute, 16) : legacyProviderIndex;
                          provider.tag = providerTag;
  
                          provider.infoTag = parser.getAttributeValue(null, "info_tag");
                          if (!TextUtils.isEmpty(provider.infoTag) && !mSafeMode) {
                              AppWidgetProviderInfo info = parseAppWidgetProviderInfo(
                                      providerId, providerInfo, provider.infoTag);
                              if (info != null) {
                                  provider.info = info;
                              }
                          }
                      } else if ("h".equals(tag)) {
                          legacyHostIndex++;
                          Host host = new Host();
                          // TODO: do we need to check that this package has the same signature
                          // as before?
                          String pkg = parser.getAttributeValue(null, "pkg");
  
                          final int uid = getUidForPackage(pkg, userId);
                          if (uid < 0) {
                              host.zombie = true;
                          }
  
                          if (!host.zombie || mSafeMode) {
                              // In safe mode, we don't discard the hosts we don't recognize
                              // so that they're not pruned from our list. Otherwise, we do.
                              final int hostId = Integer.parseInt(parser.getAttributeValue(
                                      null, "id"), 16);
  
                              String tagAttribute = parser.getAttributeValue(null, "tag");
                              final int hostTag = !TextUtils.isEmpty(tagAttribute)
                                      ? Integer.parseInt(tagAttribute, 16) : legacyHostIndex;
  
                              host.tag = hostTag;
                              host.id = new HostId(uid, hostId, pkg);
                              mHosts.add(host);
                          }
                      } else if ("b".equals(tag)) {
                          String packageName = parser.getAttributeValue(null, "packageName");
                          final int uid = getUidForPackage(packageName, userId);
                          if (uid >= 0) {
                              Pair<Integer, String> packageId = Pair.create(userId, packageName);
                              mPackagesWithBindWidgetPermission.add(packageId);
                          }
                      } else if ("g".equals(tag)) {
                          Widget widget = new Widget();
                          widget.appWidgetId = Integer.parseInt(parser.getAttributeValue(
                                  null, "id"), 16);
                          setMinAppWidgetIdLocked(userId, widget.appWidgetId + 1);
  
                          // restored ID is allowed to be absent
                          String restoredIdString = parser.getAttributeValue(null, "rid");
                          widget.restoredId = (restoredIdString == null) ? 0
                                  : Integer.parseInt(restoredIdString, 16);
  
                          Bundle options = new Bundle();
                          String minWidthString = parser.getAttributeValue(null, "min_width");
                          if (minWidthString != null) {
                              options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
                                      Integer.parseInt(minWidthString, 16));
                          }
                          String minHeightString = parser.getAttributeValue(null, "min_height");
                          if (minHeightString != null) {
                              options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
                                      Integer.parseInt(minHeightString, 16));
                          }
                          String maxWidthString = parser.getAttributeValue(null, "max_width");
                          if (maxWidthString != null) {
                              options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
                                      Integer.parseInt(maxWidthString, 16));
                          }
                          String maxHeightString = parser.getAttributeValue(null, "max_height");
                          if (maxHeightString != null) {
                              options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
                                      Integer.parseInt(maxHeightString, 16));
                          }
                          String categoryString = parser.getAttributeValue(null, "host_category");
                          if (categoryString != null) {
                              options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
                                      Integer.parseInt(categoryString, 16));
                          }
                          widget.options = options;
  
                          final int hostTag = Integer.parseInt(parser.getAttributeValue(
                                  null, "h"), 16);
                          String providerString = parser.getAttributeValue(null, "p");
                          final int providerTag = (providerString != null) ? Integer.parseInt(
                                  parser.getAttributeValue(null, "p"), 16) : TAG_UNDEFINED;
  
                          // We can match widgets with hosts and providers only after hosts
                          // and providers for all users have been loaded since the widget
                          // host and provider can be in different user profiles.
                          LoadedWidgetState loadedWidgets = new LoadedWidgetState(widget,
                                  hostTag, providerTag);
                          outLoadedWidgets.add(loadedWidgets);
                      }
                  }
              } while (type != XmlPullParser.END_DOCUMENT);
          } catch (NullPointerException
                  | NumberFormatException
                  | XmlPullParserException
                  | IOException
                  | IndexOutOfBoundsException e) {
              Slog.w(TAG, "failed parsing " + e);
              return -1;
          }
  
          return version;
      }
  
      private void performUpgradeLocked(int fromVersion) {
          if (fromVersion < CURRENT_VERSION) {
              Slog.v(TAG, "Upgrading widget database from " + fromVersion + " to "
                      + CURRENT_VERSION);
          }
  
          int version = fromVersion;
  
          // Update 1: keyguard moved from package "android" to "com.android.keyguard"
          if (version == 0) {
              HostId oldHostId = new HostId(Process.myUid(),
                      KEYGUARD_HOST_ID, OLD_KEYGUARD_HOST_PACKAGE);
  
              Host host = lookupHostLocked(oldHostId);
              if (host != null) {
                  final int uid = getUidForPackage(NEW_KEYGUARD_HOST_PACKAGE,
                          UserHandle.USER_SYSTEM);
                  if (uid >= 0) {
                      host.id = new HostId(uid, KEYGUARD_HOST_ID, NEW_KEYGUARD_HOST_PACKAGE);
                  }
              }
  
              version = 1;
          }
  
          if (version != CURRENT_VERSION) {
              throw new IllegalStateException("Failed to upgrade widget database");
          }
      }
  
      private static File getStateFile(int userId) {
          return new File(Environment.getUserSystemDirectory(userId), STATE_FILENAME);
      }
  
      private static AtomicFile getSavedStateFile(int userId) {
          File dir = Environment.getUserSystemDirectory(userId);
          File settingsFile = getStateFile(userId);
          if (!settingsFile.exists() && userId == UserHandle.USER_SYSTEM) {
              if (!dir.exists()) {
                  dir.mkdirs();
              }
              // Migrate old data
              File oldFile = new File("/data/system/" + STATE_FILENAME);
              // Method doesn't throw an exception on failure. Ignore any errors
              // in moving the file (like non-existence)
              oldFile.renameTo(settingsFile);
          }
          return new AtomicFile(settingsFile);
      }
  
      void onUserStopped(int userId) {
          synchronized (mLock) {
              boolean crossProfileWidgetsChanged = false;
  
              // Remove widgets that have both host and provider in the user.
              final int widgetCount = mWidgets.size();
              for (int i = widgetCount - 1; i >= 0; i--) {
                  Widget widget = mWidgets.get(i);
  
                  final boolean hostInUser = widget.host.getUserId() == userId;
                  final boolean hasProvider = widget.provider != null;
                  final boolean providerInUser = hasProvider && widget.provider.getUserId() == userId;
  
                  // If both host and provider are in the user, just drop the widgets
                  // as we do not want to make host callbacks and provider broadcasts
                  // as the host and the provider will be killed.
                  if (hostInUser && (!hasProvider || providerInUser)) {
                      removeWidgetLocked(widget);
                      widget.host.widgets.remove(widget);
                      widget.host = null;
                      if (hasProvider) {
                          widget.provider.widgets.remove(widget);
                          widget.provider = null;
                      }
                  }
              }
  
              // Remove hosts and notify providers in other profiles.
              final int hostCount = mHosts.size();
              for (int i = hostCount - 1; i >= 0; i--) {
                  Host host = mHosts.get(i);
                  if (host.getUserId() == userId) {
                      crossProfileWidgetsChanged |= !host.widgets.isEmpty();
                      deleteHostLocked(host);
                  }
              }
  
              // Leave the providers present as hosts will show the widgets
              // masked while the user is stopped.
  
              // Remove grants for this user.
              final int grantCount = mPackagesWithBindWidgetPermission.size();
              for (int i = grantCount - 1; i >= 0; i--) {
                  Pair<Integer, String> packageId = mPackagesWithBindWidgetPermission.valueAt(i);
                  if (packageId.first == userId) {
                      mPackagesWithBindWidgetPermission.removeAt(i);
                  }
              }
  
              // Take a note we no longer have state for this user.
              final int userIndex = mLoadedUserIds.indexOfKey(userId);
              if (userIndex >= 0) {
                  mLoadedUserIds.removeAt(userIndex);
              }
  
              // Remove the widget id counter.
              final int nextIdIndex = mNextAppWidgetIds.indexOfKey(userId);
              if (nextIdIndex >= 0) {
                  mNextAppWidgetIds.removeAt(nextIdIndex);
              }
  
              // Save state if removing a profile changed the group state.
              // Nothing will be saved if the group parent was removed.
              if (crossProfileWidgetsChanged) {
                  saveGroupStateAsync(userId);
              }
          }
      }
  
      /**
       * Updates all providers with the specified package names, and records any providers that were
       * pruned.
       *
       * @return whether any providers were updated
       */
      private boolean updateProvidersForPackageLocked(String packageName, int userId,
              Set<ProviderId> removedProviders) {
          boolean providersUpdated = false;
  
          HashSet<ProviderId> keep = new HashSet<>();
          Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
          intent.setPackage(packageName);
          List<ResolveInfo> broadcastReceivers = queryIntentReceivers(intent, userId);
  
          // add the missing ones and collect which ones to keep
          int N = broadcastReceivers == null ? 0 : broadcastReceivers.size();
          for (int i = 0; i < N; i++) {
              ResolveInfo ri = broadcastReceivers.get(i);
              ActivityInfo ai = ri.activityInfo;
  
              if ((ai.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
                  continue;
              }
  
              if (packageName.equals(ai.packageName)) {
                  ProviderId providerId = new ProviderId(ai.applicationInfo.uid,
                          new ComponentName(ai.packageName, ai.name));
  
                  Provider provider = lookupProviderLocked(providerId);
                  if (provider == null) {
                      if (addProviderLocked(ri)) {
                          keep.add(providerId);
                          providersUpdated = true;
                      }
                  } else {
                      Provider parsed = parseProviderInfoXml(providerId, ri, provider);
                      if (parsed != null) {
                          keep.add(providerId);
                          // Use the new AppWidgetProviderInfo.
                          provider.info = parsed.info;
                          // If it's enabled
                          final int M = provider.widgets.size();
                          if (M > 0) {
                              int[] appWidgetIds = getWidgetIds(provider.widgets);
                              // Reschedule for the new updatePeriodMillis (don't worry about handling
                              // it specially if updatePeriodMillis didn't change because we just sent
                              // an update, and the next one will be updatePeriodMillis from now).
                              cancelBroadcastsLocked(provider);
                              registerForBroadcastsLocked(provider, appWidgetIds);
                              // If it's currently showing, call back with the new
                              // AppWidgetProviderInfo.
                              for (int j = 0; j < M; j++) {
                                  Widget widget = provider.widgets.get(j);
                                  widget.views = null;
                                  scheduleNotifyProviderChangedLocked(widget);
                              }
                              // Now that we've told the host, push out an update.
                              sendUpdateIntentLocked(provider, appWidgetIds);
                          }
                      }
                      providersUpdated = true;
                  }
              }
          }
  
          // prune the ones we don't want to keep
          N = mProviders.size();
          for (int i = N - 1; i >= 0; i--) {
              Provider provider = mProviders.get(i);
              if (packageName.equals(provider.info.provider.getPackageName())
                      && provider.getUserId() == userId
                      && !keep.contains(provider.id)) {
                  if (removedProviders != null) {
                      removedProviders.add(provider.id);
                  }
                  deleteProviderLocked(provider);
                  providersUpdated = true;
              }
          }
  
          return providersUpdated;
      }
  
      // Remove widgets for provider in userId that are hosted in parentUserId
      private void removeWidgetsForPackageLocked(String pkgName, int userId, int parentUserId) {
          final int N = mProviders.size();
          for (int i = 0; i < N; ++i) {
              Provider provider = mProviders.get(i);
              if (pkgName.equals(provider.info.provider.getPackageName())
                      && provider.getUserId() == userId
                      && provider.widgets.size() > 0) {
                  deleteWidgetsLocked(provider, parentUserId);
              }
          }
      }
  
      private boolean removeProvidersForPackageLocked(String pkgName, int userId) {
          boolean removed = false;
  
          final int N = mProviders.size();
          for (int i = N - 1; i >= 0; i--) {
              Provider provider = mProviders.get(i);
              if (pkgName.equals(provider.info.provider.getPackageName())
                      && provider.getUserId() == userId) {
                  deleteProviderLocked(provider);
                  removed = true;
              }
          }
          return removed;
      }
  
      private boolean removeHostsAndProvidersForPackageLocked(String pkgName, int userId) {
          boolean removed = removeProvidersForPackageLocked(pkgName, userId);
  
          // Delete the hosts for this package too
          // By now, we have removed any AppWidgets that were in any hosts here,
          // so we don't need to worry about sending DISABLE broadcasts to them.
          final int N = mHosts.size();
          for (int i = N - 1; i >= 0; i--) {
              Host host = mHosts.get(i);
              if (pkgName.equals(host.id.packageName)
                      && host.getUserId() == userId) {
                  deleteHostLocked(host);
                  removed = true;
              }
          }
  
          return removed;
      }
  
      private String getCanonicalPackageName(String packageName, String className, int userId) {
          final long identity = Binder.clearCallingIdentity();
          try {
              try {
                  AppGlobals.getPackageManager().getReceiverInfo(new ComponentName(packageName,
                          className), 0, userId);
                  return packageName;
              } catch (RemoteException re) {
                  String[] packageNames = mContext.getPackageManager()
                          .currentToCanonicalPackageNames(new String[]{packageName});
                  if (packageNames != null && packageNames.length > 0) {
                      return packageNames[0];
                  }
              }
          } finally {
              Binder.restoreCallingIdentity(identity);
          }
          return null;
      }
  
      private void sendBroadcastAsUser(Intent intent, UserHandle userHandle) {
          final long identity = Binder.clearCallingIdentity();
          try {
              mContext.sendBroadcastAsUser(intent, userHandle);
          } finally {
              Binder.restoreCallingIdentity(identity);
          }
      }
  
      private void bindService(Intent intent, ServiceConnection connection,
              UserHandle userHandle) {
          final long token = Binder.clearCallingIdentity();
          try {
              mContext.bindServiceAsUser(intent, connection,
                      Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
                      userHandle);
          } finally {
              Binder.restoreCallingIdentity(token);
          }
      }
  
      private void unbindService(ServiceConnection connection) {
          final long token = Binder.clearCallingIdentity();
          try {
              mContext.unbindService(connection);
          } finally {
              Binder.restoreCallingIdentity(token);
          }
      }
  
      @Override
      public void onCrossProfileWidgetProvidersChanged(int userId, List<String> packages) {
          final int parentId = mSecurityPolicy.getProfileParent(userId);
          // We care only if the white-listed package is in a profile of
          // the group parent as only the parent can add widgets from the
          // profile and not the other way around.
          if (parentId != userId) {
              synchronized (mLock) {
                  boolean providersChanged = false;
  
                  ArraySet<String> previousPackages = new ArraySet<String>();
                  final int providerCount = mProviders.size();
                  for (int i = 0; i < providerCount; ++i) {
                      Provider provider = mProviders.get(i);
                      if (provider.getUserId() == userId) {
                          previousPackages.add(provider.id.componentName.getPackageName());
                      }
                  }
  
                  final int packageCount = packages.size();
                  for (int i = 0; i < packageCount; i++) {
                      String packageName = packages.get(i);
                      previousPackages.remove(packageName);
                      providersChanged |= updateProvidersForPackageLocked(packageName,
                              userId, null);
                  }
  
                  // Remove widgets from hosts in parent user for packages not in the whitelist
                  final int removedCount = previousPackages.size();
                  for (int i = 0; i < removedCount; ++i) {
                      removeWidgetsForPackageLocked(previousPackages.valueAt(i),
                              userId, parentId);
                  }
  
                  if (providersChanged || removedCount > 0) {
                      saveGroupStateAsync(userId);
                      scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
                  }
              }
          }
      }
  
      private boolean isProfileWithLockedParent(int userId) {
          long token = Binder.clearCallingIdentity();
          try {
              UserInfo userInfo = mUserManager.getUserInfo(userId);
              if (userInfo != null && userInfo.isManagedProfile()) {
                  UserInfo parentInfo = mUserManager.getProfileParent(userId);
                  if (parentInfo != null
                          && !isUserRunningAndUnlocked(parentInfo.getUserHandle().getIdentifier())) {
                      return true;
                  }
              }
          } finally {
              Binder.restoreCallingIdentity(token);
          }
          return false;
      }
  
      private boolean isProfileWithUnlockedParent(int userId) {
          UserInfo userInfo = mUserManager.getUserInfo(userId);
          if (userInfo != null && userInfo.isManagedProfile()) {
              UserInfo parentInfo = mUserManager.getProfileParent(userId);
              if (parentInfo != null
                      && mUserManager.isUserUnlockingOrUnlocked(parentInfo.getUserHandle())) {
                  return true;
              }
          }
          return false;
      }
  
      private final class CallbackHandler extends Handler {
          public static final int MSG_NOTIFY_UPDATE_APP_WIDGET = 1;
          public static final int MSG_NOTIFY_PROVIDER_CHANGED = 2;
          public static final int MSG_NOTIFY_PROVIDERS_CHANGED = 3;
          public static final int MSG_NOTIFY_VIEW_DATA_CHANGED = 4;
  
          public CallbackHandler(Looper looper) {
              super(looper, null, false);
          }
  
          @Override
          public void handleMessage(Message message) {
              switch (message.what) {
                  case MSG_NOTIFY_UPDATE_APP_WIDGET: {
                      SomeArgs args = (SomeArgs) message.obj;
                      Host host = (Host) args.arg1;
                      IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
                      RemoteViews views = (RemoteViews) args.arg3;
                      long requestId = (Long) args.arg4;
                      final int appWidgetId = args.argi1;
                      args.recycle();
  
                      handleNotifyUpdateAppWidget(host, callbacks, appWidgetId, views, requestId);
                  } break;
  
                  case MSG_NOTIFY_PROVIDER_CHANGED: {
                      SomeArgs args = (SomeArgs) message.obj;
                      Host host = (Host) args.arg1;
                      IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
                      AppWidgetProviderInfo info = (AppWidgetProviderInfo)args.arg3;
                      long requestId = (Long) args.arg4;
                      final int appWidgetId = args.argi1;
                      args.recycle();
  
                      handleNotifyProviderChanged(host, callbacks, appWidgetId, info, requestId);
                  } break;
  
                  case MSG_NOTIFY_PROVIDERS_CHANGED: {
                      SomeArgs args = (SomeArgs) message.obj;
                      Host host = (Host) args.arg1;
                      IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
                      args.recycle();
  
                      handleNotifyProvidersChanged(host, callbacks);
                  } break;
  
                  case MSG_NOTIFY_VIEW_DATA_CHANGED: {
                      SomeArgs args = (SomeArgs) message.obj;
                      Host host = (Host) args.arg1;
                      IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
                      long requestId = (Long) args.arg3;
                      final int appWidgetId = args.argi1;
                      final int viewId = args.argi2;
                      args.recycle();
  
                      handleNotifyAppWidgetViewDataChanged(host, callbacks, appWidgetId, viewId,
                              requestId);
                  } break;
              }
          }
      }
  
      private final class SecurityPolicy {
  
          public boolean isEnabledGroupProfile(int profileId) {
              final int parentId = UserHandle.getCallingUserId();
              return isParentOrProfile(parentId, profileId) && isProfileEnabled(profileId);
          }
  
          public int[] getEnabledGroupProfileIds(int userId) {
              final int parentId = getGroupParent(userId);
  
              final long identity = Binder.clearCallingIdentity();
              try {
                  return mUserManager.getEnabledProfileIds(parentId);
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          public void enforceServiceExistsAndRequiresBindRemoteViewsPermission(
                  ComponentName componentName, int userId) {
              final long identity = Binder.clearCallingIdentity();
              try {
                  ServiceInfo serviceInfo = mPackageManager.getServiceInfo(componentName,
                          PackageManager.GET_PERMISSIONS, userId);
                  if (serviceInfo == null) {
                      throw new SecurityException("Service " + componentName
                              + " not installed for user " + userId);
                  }
                  if (!android.Manifest.permission.BIND_REMOTEVIEWS.equals(serviceInfo.permission)) {
                      throw new SecurityException("Service " + componentName
                              + " in user " + userId + "does not require "
                              + android.Manifest.permission.BIND_REMOTEVIEWS);
                  }
              } catch (RemoteException re) {
                  // Local call - shouldn't happen.
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
          }
  
          public void enforceModifyAppWidgetBindPermissions(String packageName) {
              mContext.enforceCallingPermission(
                      android.Manifest.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS,
                      "hasBindAppWidgetPermission packageName=" + packageName);
          }
  
          public boolean isCallerInstantAppLocked() {
              final int callingUid =  Binder.getCallingUid();
              final long identity = Binder.clearCallingIdentity();
              try {
                  final String[] uidPackages = mPackageManager.getPackagesForUid(callingUid);
                  if (!ArrayUtils.isEmpty(uidPackages)) {
                      return mPackageManager.isInstantApp(uidPackages[0],
                              UserHandle.getCallingUserId());
                  }
              } catch (RemoteException e) {
                  /* ignore - same process */
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
              return false;
          }
  
          public boolean isInstantAppLocked(String packageName, int userId) {
              final long identity = Binder.clearCallingIdentity();
              try {
                  return mPackageManager.isInstantApp(packageName, userId);
              } catch (RemoteException e) {
                  /* ignore - same process */
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
              return false;
          }
  
          public void enforceCallFromPackage(String packageName) {
              mAppOpsManager.checkPackage(Binder.getCallingUid(), packageName);
          }
  
          public boolean hasCallerBindPermissionOrBindWhiteListedLocked(String packageName) {
              try {
                  mContext.enforceCallingOrSelfPermission(
                          android.Manifest.permission.BIND_APPWIDGET, null);
              } catch (SecurityException se) {
                  if (!isCallerBindAppWidgetWhiteListedLocked(packageName)) {
                      return false;
                  }
              }
              return true;
          }
  
          private boolean isCallerBindAppWidgetWhiteListedLocked(String packageName) {
              final int userId = UserHandle.getCallingUserId();
              final int packageUid = getUidForPackage(packageName, userId);
              if (packageUid < 0) {
                  throw new IllegalArgumentException("No package " + packageName
                          + " for user " + userId);
              }
              synchronized (mLock) {
                  ensureGroupStateLoadedLocked(userId);
  
                  Pair<Integer, String> packageId = Pair.create(userId, packageName);
                  if (mPackagesWithBindWidgetPermission.contains(packageId)) {
                      return true;
                  }
              }
  
              return false;
          }
  
          public boolean canAccessAppWidget(Widget widget, int uid, String packageName) {
              if (isHostInPackageForUid(widget.host, uid, packageName)) {
                  // Apps hosting the AppWidget have access to it.
                  return true;
              }
              if (isProviderInPackageForUid(widget.provider, uid, packageName)) {
                  // Apps providing the AppWidget have access to it.
                  return true;
              }
              if (isHostAccessingProvider(widget.host, widget.provider, uid, packageName)) {
                  // Apps hosting the AppWidget get to bind to a remote view service in the provider.
                  return true;
              }
              final int userId = UserHandle.getUserId(uid);
              if ((widget.host.getUserId() == userId || (widget.provider != null
                      && widget.provider.getUserId() == userId))
                  && mContext.checkCallingPermission(android.Manifest.permission.BIND_APPWIDGET)
                      == PackageManager.PERMISSION_GRANTED) {
                  // Apps that run in the same user as either the host or the provider and
                  // have the bind widget permission have access to the widget.
                  return true;
              }
              return false;
          }
  
          private boolean isParentOrProfile(int parentId, int profileId) {
              if (parentId == profileId) {
                  return true;
              }
              return getProfileParent(profileId) == parentId;
          }
  
          public boolean isProviderInCallerOrInProfileAndWhitelListed(String packageName,
                  int profileId) {
              final int callerId = UserHandle.getCallingUserId();
              if (profileId == callerId) {
                  return true;
              }
              final int parentId = getProfileParent(profileId);
              if (parentId != callerId) {
                  return false;
              }
              return isProviderWhiteListed(packageName, profileId);
          }
  
          public boolean isProviderWhiteListed(String packageName, int profileId) {
              // If the policy manager is not available on the device we deny it all.
              if (mDevicePolicyManagerInternal == null) {
                  return false;
              }
  
              List<String> crossProfilePackages = mDevicePolicyManagerInternal
                      .getCrossProfileWidgetProviders(profileId);
  
              return crossProfilePackages.contains(packageName);
          }
  
          public int getProfileParent(int profileId) {
              final long identity = Binder.clearCallingIdentity();
              try {
                  UserInfo parent = mUserManager.getProfileParent(profileId);
                  if (parent != null) {
                      return parent.getUserHandle().getIdentifier();
                  }
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
              return UNKNOWN_USER_ID;
          }
  
          public int getGroupParent(int profileId) {
              final int parentId = mSecurityPolicy.getProfileParent(profileId);
              return (parentId != UNKNOWN_USER_ID) ? parentId : profileId;
          }
  
          public boolean isHostInPackageForUid(Host host, int uid, String packageName) {
              return host.id.uid == uid && host.id.packageName.equals(packageName);
          }
  
          public boolean isProviderInPackageForUid(Provider provider, int uid,
                  String packageName) {
              // Packages providing the AppWidget have access to it.
              return provider != null && provider.id.uid == uid
                      && provider.id.componentName.getPackageName().equals(packageName);
          }
  
          public boolean isHostAccessingProvider(Host host, Provider provider, int uid,
                  String packageName) {
              // The host creates a package context to bind to remote views service in the provider.
              return host.id.uid == uid && provider != null
                      && provider.id.componentName.getPackageName().equals(packageName);
          }
  
          private boolean isProfileEnabled(int profileId) {
              final long identity = Binder.clearCallingIdentity();
              try {
                  UserInfo userInfo = mUserManager.getUserInfo(profileId);
                  if (userInfo == null || !userInfo.isEnabled()) {
                      return false;
                  }
              } finally {
                  Binder.restoreCallingIdentity(identity);
              }
              return true;
          }
      }
  
      private static final class Provider {
          ProviderId id;
          AppWidgetProviderInfo info;
          ArrayList<Widget> widgets = new ArrayList<>();
          PendingIntent broadcast;
          String infoTag;
          boolean zombie; // if we're in safe mode, don't prune this just because nobody references it
  
          boolean maskedByLockedProfile;
          boolean maskedByQuietProfile;
          boolean maskedBySuspendedPackage;
  
          int tag = TAG_UNDEFINED; // for use while saving state (the index)
  
          public int getUserId() {
              return UserHandle.getUserId(id.uid);
          }
  
          public boolean isInPackageForUser(String packageName, int userId) {
              return getUserId() == userId
                      && id.componentName.getPackageName().equals(packageName);
          }
  
          // is there an instance of this provider hosted by the given app?
          public boolean hostedByPackageForUser(String packageName, int userId) {
              final int N = widgets.size();
              for (int i = 0; i < N; i++) {
                  Widget widget = widgets.get(i);
                  if (packageName.equals(widget.host.id.packageName)
                          && widget.host.getUserId() == userId) {
                      return true;
                  }
              }
              return false;
          }
  
          @Override
          public String toString() {
              return "Provider{" + id + (zombie ? " Z" : "") + '}';
          }
  
          // returns true if it's different from previous state.
          public boolean setMaskedByQuietProfileLocked(boolean masked) {
              boolean oldState = maskedByQuietProfile;
              maskedByQuietProfile = masked;
              return masked != oldState;
          }
  
          // returns true if it's different from previous state.
          public boolean setMaskedByLockedProfileLocked(boolean masked) {
              boolean oldState = maskedByLockedProfile;
              maskedByLockedProfile = masked;
              return masked != oldState;
          }
  
          // returns true if it's different from previous state.
          public boolean setMaskedBySuspendedPackageLocked(boolean masked) {
              boolean oldState = maskedBySuspendedPackage;
              maskedBySuspendedPackage = masked;
              return masked != oldState;
          }
  
          public boolean isMaskedLocked() {
              return maskedByQuietProfile || maskedByLockedProfile || maskedBySuspendedPackage;
          }
  
          public boolean shouldBePersisted() {
              return !widgets.isEmpty() || !TextUtils.isEmpty(infoTag);
          }
      }
  
      private static final class ProviderId {
          final int uid;
          final ComponentName componentName;
  
          private ProviderId(int uid, ComponentName componentName) {
              this.uid = uid;
              this.componentName = componentName;
          }
  
          @Override
          public boolean equals(Object obj) {
              if (this == obj) {
                  return true;
              }
              if (obj == null) {
                  return false;
              }
              if (getClass() != obj.getClass()) {
                  return false;
              }
              ProviderId other = (ProviderId) obj;
              if (uid != other.uid)  {
                  return false;
              }
              if (componentName == null) {
                  if (other.componentName != null) {
                      return false;
                  }
              } else if (!componentName.equals(other.componentName)) {
                  return false;
              }
              return true;
          }
  
          @Override
          public int hashCode() {
              int result = uid;
              result = 31 * result + ((componentName != null)
                      ? componentName.hashCode() : 0);
              return result;
          }
  
          @Override
          public String toString() {
              return "ProviderId{user:" + UserHandle.getUserId(uid) + ", app:"
                      + UserHandle.getAppId(uid) + ", cmp:" + componentName + '}';
          }
      }
  
      private static final class Host {
          HostId id;
          ArrayList<Widget> widgets = new ArrayList<>();
          IAppWidgetHost callbacks;
          boolean zombie; // if we're in safe mode, don't prune this just because nobody references it
  
          int tag = TAG_UNDEFINED; // for use while saving state (the index)
          // Sequence no for the last update successfully sent. This is updated whenever a
          // widget update is successfully sent to the host callbacks. As all new/undelivered updates
          // will have sequenceNo greater than this, all those updates will be sent when the host
          // callbacks are attached again.
          long lastWidgetUpdateSequenceNo;
  
          public int getUserId() {
              return UserHandle.getUserId(id.uid);
          }
  
          public boolean isInPackageForUser(String packageName, int userId) {
              return getUserId() == userId && id.packageName.equals(packageName);
          }
  
          private boolean hostsPackageForUser(String pkg, int userId) {
              final int N = widgets.size();
              for (int i = 0; i < N; i++) {
                  Provider provider = widgets.get(i).provider;
                  if (provider != null && provider.getUserId() == userId && provider.info != null
                          && pkg.equals(provider.info.provider.getPackageName())) {
                      return true;
                  }
              }
              return false;
          }
  
          /**
           * Adds all pending updates in {@param outUpdates} keys by the update time.
           */
          public boolean getPendingUpdatesForId(int appWidgetId,
                  LongSparseArray<PendingHostUpdate> outUpdates) {
              long updateSequenceNo = lastWidgetUpdateSequenceNo;
              int N = widgets.size();
              for (int i = 0; i < N; i++) {
                  Widget widget = widgets.get(i);
                  if (widget.appWidgetId == appWidgetId) {
                      outUpdates.clear();
                      for (int j = widget.updateSequenceNos.size() - 1; j >= 0; j--) {
                          long requestId = widget.updateSequenceNos.valueAt(j);
                          if (requestId <= updateSequenceNo) {
                              continue;
                          }
                          int id = widget.updateSequenceNos.keyAt(j);
                          final PendingHostUpdate update;
                          switch (id) {
                              case ID_PROVIDER_CHANGED:
                                  update = PendingHostUpdate.providerChanged(
                                          appWidgetId, widget.provider.info);
                                  break;
                              case ID_VIEWS_UPDATE:
                                  update = PendingHostUpdate.updateAppWidget(appWidgetId,
                                          cloneIfLocalBinder(widget.getEffectiveViewsLocked()));
                                  break;
                              default:
                                  update = PendingHostUpdate.viewDataChanged(appWidgetId, id);
                          }
                          outUpdates.put(requestId, update);
                      }
                      return true;
                  }
              }
              return false;
          }
  
          @Override
          public String toString() {
              return "Host{" + id + (zombie ? " Z" : "") + '}';
          }
      }
  
      private static final class HostId {
          final int uid;
          final int hostId;
          final String packageName;
  
          public HostId(int uid, int hostId, String packageName) {
              this.uid = uid;
              this.hostId = hostId;
              this.packageName = packageName;
          }
  
          @Override
          public boolean equals(Object obj) {
              if (this == obj) {
                  return true;
              }
              if (obj == null) {
                  return false;
              }
              if (getClass() != obj.getClass()) {
                  return false;
              }
              HostId other = (HostId) obj;
              if (uid != other.uid)  {
                  return false;
              }
              if (hostId != other.hostId) {
                  return false;
              }
              if (packageName == null) {
                  if (other.packageName != null) {
                      return false;
                  }
              } else if (!packageName.equals(other.packageName)) {
                  return false;
              }
              return true;
          }
  
          @Override
          public int hashCode() {
              int result = uid;
              result = 31 * result + hostId;
              result = 31 * result + ((packageName != null)
                      ? packageName.hashCode() : 0);
              return result;
          }
  
          @Override
          public String toString() {
              return "HostId{user:" + UserHandle.getUserId(uid) + ", app:"
                      + UserHandle.getAppId(uid) + ", hostId:" + hostId
                      + ", pkg:" + packageName + '}';
          }
      }
  
      // These can be any constants that would not collide with a resource id.
      private static final int ID_VIEWS_UPDATE = 0;
      private static final int ID_PROVIDER_CHANGED = 1;
  
      private static final class Widget {
          int appWidgetId;
          int restoredId;  // tracking & remapping any restored state
          Provider provider;
          RemoteViews views;
          RemoteViews maskedViews;
          Bundle options;
          Host host;
          // Map of request type to updateSequenceNo.
          SparseLongArray updateSequenceNos = new SparseLongArray(2);
  
          @Override
          public String toString() {
              return "AppWidgetId{" + appWidgetId + ':' + host + ':' + provider + '}';
          }
  
          private boolean replaceWithMaskedViewsLocked(RemoteViews views) {
              maskedViews = views;
              return true;
          }
  
          private boolean clearMaskedViewsLocked() {
              if (maskedViews != null) {
                  maskedViews = null;
                  return true;
              } else {
                  return false;
              }
          }
  
          public RemoteViews getEffectiveViewsLocked() {
              return maskedViews != null ? maskedViews : views;
          }
      }
  
      private class LoadedWidgetState {
          final Widget widget;
          final int hostTag;
          final int providerTag;
  
          public LoadedWidgetState(Widget widget, int hostTag, int providerTag) {
              this.widget = widget;
              this.hostTag = hostTag;
              this.providerTag = providerTag;
          }
      }
  
      private final class SaveStateRunnable implements Runnable {
          final int mUserId;
  
          public SaveStateRunnable(int userId) {
              mUserId = userId;
          }
  
          @Override
          public void run() {
              synchronized (mLock) {
                  // No need to enforce unlocked state when there is no caller. User can be in the
                  // stopping state or removed by the time the message is processed
                  ensureGroupStateLoadedLocked(mUserId, false /* enforceUserUnlockingOrUnlocked */ );
                  saveStateLocked(mUserId);
              }
          }
      }
  
      /**
       * This class encapsulates the backup and restore logic for a user group state.
       */
      private final class BackupRestoreController {
          private static final String TAG = "BackupRestoreController";
  
          private static final boolean DEBUG = true;
  
          // Version of backed-up widget state.
          private static final int WIDGET_STATE_VERSION = 2;
  
          // We need to make sure to wipe the pre-restore widget state only once for
          // a given package.  Keep track of what we've done so far here; the list is
          // cleared at the start of every system restore pass, but preserved through
          // any install-time restore operations.
          private final HashSet<String> mPrunedApps = new HashSet<>();
  
          private final HashMap<Provider, ArrayList<RestoreUpdateRecord>> mUpdatesByProvider =
                  new HashMap<>();
          private final HashMap<Host, ArrayList<RestoreUpdateRecord>> mUpdatesByHost =
                  new HashMap<>();
  
          public List<String> getWidgetParticipants(int userId) {
              if (DEBUG) {
                  Slog.i(TAG, "Getting widget participants for user: " + userId);
              }
  
              HashSet<String> packages = new HashSet<>();
              synchronized (mLock) {
                  final int N = mWidgets.size();
                  for (int i = 0; i < N; i++) {
                      Widget widget = mWidgets.get(i);
  
                      // Skip cross-user widgets.
                      if (!isProviderAndHostInUser(widget, userId)) {
                          continue;
                      }
  
                      packages.add(widget.host.id.packageName);
                      Provider provider = widget.provider;
                      if (provider != null) {
                          packages.add(provider.id.componentName.getPackageName());
                      }
                  }
              }
              return new ArrayList<>(packages);
          }
  
          public byte[] getWidgetState(String backedupPackage, int userId) {
              if (DEBUG) {
                  Slog.i(TAG, "Getting widget state for user: " + userId);
              }
  
              ByteArrayOutputStream stream = new ByteArrayOutputStream();
              synchronized (mLock) {
                  // Preflight: if this app neither hosts nor provides any live widgets
                  // we have no work to do.
                  if (!packageNeedsWidgetBackupLocked(backedupPackage, userId)) {
                      return null;
                  }
  
                  try {
                      XmlSerializer out = new FastXmlSerializer();
                      out.setOutput(stream, StandardCharsets.UTF_8.name());
                      out.startDocument(null, true);
                      out.startTag(null, "ws");      // widget state
                      out.attribute(null, "version", String.valueOf(WIDGET_STATE_VERSION));
                      out.attribute(null, "pkg", backedupPackage);
  
                      // Remember all the providers that are currently hosted or published
                      // by this package: that is, all of the entities related to this app
                      // which will need to be told about id remapping.
                      int index = 0;
                      int N = mProviders.size();
                      for (int i = 0; i < N; i++) {
                          Provider provider = mProviders.get(i);
  
                          if (provider.shouldBePersisted()
                                  && (provider.isInPackageForUser(backedupPackage, userId)
                                  || provider.hostedByPackageForUser(backedupPackage, userId))) {
                              provider.tag = index;
                              serializeProvider(out, provider);
                              index++;
                          }
                      }
  
                      N = mHosts.size();
                      index = 0;
                      for (int i = 0; i < N; i++) {
                          Host host = mHosts.get(i);
  
                          if (!host.widgets.isEmpty()
                                  && (host.isInPackageForUser(backedupPackage, userId)
                                  || host.hostsPackageForUser(backedupPackage, userId))) {
                              host.tag = index;
                              serializeHost(out, host);
                              index++;
                          }
                      }
  
                      // All widget instances involving this package,
                      // either as host or as provider
                      N = mWidgets.size();
                      for (int i = 0; i < N; i++) {
                          Widget widget = mWidgets.get(i);
  
                          Provider provider = widget.provider;
                          if (widget.host.isInPackageForUser(backedupPackage, userId)
                                  || (provider != null
                                  &&  provider.isInPackageForUser(backedupPackage, userId))) {
                              serializeAppWidget(out, widget);
                          }
                      }
  
                      out.endTag(null, "ws");
                      out.endDocument();
                  } catch (IOException e) {
                      Slog.w(TAG, "Unable to save widget state for " + backedupPackage);
                      return null;
                  }
              }
  
              return stream.toByteArray();
          }
  
          public void restoreStarting(int userId) {
              if (DEBUG) {
                  Slog.i(TAG, "Restore starting for user: " + userId);
              }
  
              synchronized (mLock) {
                  // We're starting a new "system" restore operation, so any widget restore
                  // state that we see from here on is intended to replace the current
                  // widget configuration of any/all of the affected apps.
                  mPrunedApps.clear();
                  mUpdatesByProvider.clear();
                  mUpdatesByHost.clear();
              }
          }
  
          public void restoreWidgetState(String packageName, byte[] restoredState, int userId) {
              if (DEBUG) {
                  Slog.i(TAG, "Restoring widget state for user:" + userId
                          + " package: " + packageName);
              }
  
              ByteArrayInputStream stream = new ByteArrayInputStream(restoredState);
              try {
                  // Providers mentioned in the widget dataset by ordinal
                  ArrayList<Provider> restoredProviders = new ArrayList<>();
  
                  // Hosts mentioned in the widget dataset by ordinal
                  ArrayList<Host> restoredHosts = new ArrayList<>();
  
                  XmlPullParser parser = Xml.newPullParser();
                  parser.setInput(stream, StandardCharsets.UTF_8.name());
  
                  synchronized (mLock) {
                      int type;
                      do {
                          type = parser.next();
                          if (type == XmlPullParser.START_TAG) {
                              final String tag = parser.getName();
                              if ("ws".equals(tag)) {
                                  String version = parser.getAttributeValue(null, "version");
  
                                  final int versionNumber = Integer.parseInt(version);
                                  if (versionNumber > WIDGET_STATE_VERSION) {
                                      Slog.w(TAG, "Unable to process state version " + version);
                                      return;
                                  }
  
                                  // TODO: fix up w.r.t. canonical vs current package names
                                  String pkg = parser.getAttributeValue(null, "pkg");
                                  if (!packageName.equals(pkg)) {
                                      Slog.w(TAG, "Package mismatch in ws");
                                      return;
                                  }
                              } else if ("p".equals(tag)) {
                                  String pkg = parser.getAttributeValue(null, "pkg");
                                  String cl = parser.getAttributeValue(null, "cl");
  
                                  // hostedProviders index will match 'p' attribute in widget's
                                  // entry in the xml file being restored
                                  // If there's no live entry for this provider, add an inactive one
                                  // so that widget IDs referring to them can be properly allocated
  
                                  // Backup and resotre only for the parent profile.
                                  ComponentName componentName = new ComponentName(pkg, cl);
  
                                  Provider p = findProviderLocked(componentName, userId);
                                  if (p == null) {
                                      p = new Provider();
                                      p.id = new ProviderId(UNKNOWN_UID, componentName);
                                      p.info = new AppWidgetProviderInfo();
                                      p.info.provider = componentName;
                                      p.zombie = true;
                                      mProviders.add(p);
                                  }
                                  if (DEBUG) {
                                      Slog.i(TAG, "   provider " + p.id);
                                  }
                                  restoredProviders.add(p);
                              } else if ("h".equals(tag)) {
                                  // The host app may not yet exist on the device.  If it's here we
                                  // just use the existing Host entry, otherwise we create a
                                  // placeholder whose uid will be fixed up at PACKAGE_ADDED time.
                                  String pkg = parser.getAttributeValue(null, "pkg");
  
                                  final int uid = getUidForPackage(pkg, userId);
                                  final int hostId = Integer.parseInt(
                                          parser.getAttributeValue(null, "id"), 16);
  
                                  HostId id = new HostId(uid, hostId, pkg);
                                  Host h = lookupOrAddHostLocked(id);
                                  restoredHosts.add(h);
  
                                  if (DEBUG) {
                                      Slog.i(TAG, "   host[" + restoredHosts.size()
                                              + "]: {" + h.id + "}");
                                  }
                              } else if ("g".equals(tag)) {
                                  int restoredId = Integer.parseInt(
                                          parser.getAttributeValue(null, "id"), 16);
                                  int hostIndex = Integer.parseInt(
                                          parser.getAttributeValue(null, "h"), 16);
                                  Host host = restoredHosts.get(hostIndex);
                                  Provider p = null;
                                  String prov = parser.getAttributeValue(null, "p");
                                  if (prov != null) {
                                      // could have been null if the app had allocated an id
                                      // but not yet established a binding under that id
                                      int which = Integer.parseInt(prov, 16);
                                      p = restoredProviders.get(which);
                                  }
  
                                  // We'll be restoring widget state for both the host and
                                  // provider sides of this widget ID, so make sure we are
                                  // beginning from a clean slate on both fronts.
                                  pruneWidgetStateLocked(host.id.packageName, userId);
                                  if (p != null) {
                                      pruneWidgetStateLocked(p.id.componentName.getPackageName(),
                                              userId);
                                  }
  
                                  // Have we heard about this ancestral widget instance before?
                                  Widget id = findRestoredWidgetLocked(restoredId, host, p);
                                  if (id == null) {
                                      id = new Widget();
                                      id.appWidgetId = incrementAndGetAppWidgetIdLocked(userId);
                                      id.restoredId = restoredId;
                                      id.options = parseWidgetIdOptions(parser);
                                      id.host = host;
                                      id.host.widgets.add(id);
                                      id.provider = p;
                                      if (id.provider != null) {
                                          id.provider.widgets.add(id);
                                      }
                                      if (DEBUG) {
                                          Slog.i(TAG, "New restored id " + restoredId
                                                  + " now " + id);
                                      }
                                      addWidgetLocked(id);
                                  }
                                  if (id.provider != null && id.provider.info != null) {
                                      stashProviderRestoreUpdateLocked(id.provider,
                                              restoredId, id.appWidgetId);
                                  } else {
                                      Slog.w(TAG, "Missing provider for restored widget " + id);
                                  }
                                  stashHostRestoreUpdateLocked(id.host, restoredId, id.appWidgetId);
  
                                  if (DEBUG) {
                                      Slog.i(TAG, "   instance: " + restoredId
                                              + " -> " + id.appWidgetId
                                              + " :: p=" + id.provider);
                                  }
                              }
                          }
                      } while (type != XmlPullParser.END_DOCUMENT);
  
                      // We've updated our own bookkeeping.  We'll need to notify the hosts and
                      // providers about the changes, but we can't do that yet because the restore
                      // target is not necessarily fully live at this moment.  Set aside the
                      // information for now; the backup manager will call us once more at the
                      // end of the process when all of the targets are in a known state, and we
                      // will update at that point.
                  }
              } catch (XmlPullParserException | IOException e) {
                  Slog.w(TAG, "Unable to restore widget state for " + packageName);
              } finally {
                  saveGroupStateAsync(userId);
              }
          }
  
          // Called once following the conclusion of a restore operation.  This is when we
          // send out updates to apps involved in widget-state restore telling them about
          // the new widget ID space.
          public void restoreFinished(int userId) {
              if (DEBUG) {
                  Slog.i(TAG, "restoreFinished for " + userId);
              }
  
              final UserHandle userHandle = new UserHandle(userId);
              synchronized (mLock) {
                  // Build the providers' broadcasts and send them off
                  Set<Map.Entry<Provider, ArrayList<RestoreUpdateRecord>>> providerEntries
                          = mUpdatesByProvider.entrySet();
                  for (Map.Entry<Provider, ArrayList<RestoreUpdateRecord>> e : providerEntries) {
                      // For each provider there's a list of affected IDs
                      Provider provider = e.getKey();
                      ArrayList<RestoreUpdateRecord> updates = e.getValue();
                      final int pending = countPendingUpdates(updates);
                      if (DEBUG) {
                          Slog.i(TAG, "Provider " + provider + " pending: " + pending);
                      }
                      if (pending > 0) {
                          int[] oldIds = new int[pending];
                          int[] newIds = new int[pending];
                          final int N = updates.size();
                          int nextPending = 0;
                          for (int i = 0; i < N; i++) {
                              RestoreUpdateRecord r = updates.get(i);
                              if (!r.notified) {
                                  r.notified = true;
                                  oldIds[nextPending] = r.oldId;
                                  newIds[nextPending] = r.newId;
                                  nextPending++;
                                  if (DEBUG) {
                                      Slog.i(TAG, "   " + r.oldId + " => " + r.newId);
                                  }
                              }
                          }
                          sendWidgetRestoreBroadcastLocked(
                                  AppWidgetManager.ACTION_APPWIDGET_RESTORED,
                                  provider, null, oldIds, newIds, userHandle);
                      }
                  }
  
                  // same thing per host
                  Set<Map.Entry<Host, ArrayList<RestoreUpdateRecord>>> hostEntries
                          = mUpdatesByHost.entrySet();
                  for (Map.Entry<Host, ArrayList<RestoreUpdateRecord>> e : hostEntries) {
                      Host host = e.getKey();
                      if (host.id.uid != UNKNOWN_UID) {
                          ArrayList<RestoreUpdateRecord> updates = e.getValue();
                          final int pending = countPendingUpdates(updates);
                          if (DEBUG) {
                              Slog.i(TAG, "Host " + host + " pending: " + pending);
                          }
                          if (pending > 0) {
                              int[] oldIds = new int[pending];
                              int[] newIds = new int[pending];
                              final int N = updates.size();
                              int nextPending = 0;
                              for (int i = 0; i < N; i++) {
                                  RestoreUpdateRecord r = updates.get(i);
                                  if (!r.notified) {
                                      r.notified = true;
                                      oldIds[nextPending] = r.oldId;
                                      newIds[nextPending] = r.newId;
                                      nextPending++;
                                      if (DEBUG) {
                                          Slog.i(TAG, "   " + r.oldId + " => " + r.newId);
                                      }
                                  }
                              }
                              sendWidgetRestoreBroadcastLocked(
                                      AppWidgetManager.ACTION_APPWIDGET_HOST_RESTORED,
                                      null, host, oldIds, newIds, userHandle);
                          }
                      }
                  }
              }
          }
  
          private Provider findProviderLocked(ComponentName componentName, int userId) {
              final int providerCount = mProviders.size();
              for (int i = 0; i < providerCount; i++) {
                  Provider provider = mProviders.get(i);
                  if (provider.getUserId() == userId
                          && provider.id.componentName.equals(componentName)) {
                      return provider;
                  }
              }
              return null;
          }
  
          private Widget findRestoredWidgetLocked(int restoredId, Host host, Provider p) {
              if (DEBUG) {
                  Slog.i(TAG, "Find restored widget: id=" + restoredId
                          + " host=" + host + " provider=" + p);
              }
  
              if (p == null || host == null) {
                  return null;
              }
  
              final int N = mWidgets.size();
              for (int i = 0; i < N; i++) {
                  Widget widget = mWidgets.get(i);
                  if (widget.restoredId == restoredId
                          && widget.host.id.equals(host.id)
                          && widget.provider.id.equals(p.id)) {
                      if (DEBUG) {
                          Slog.i(TAG, "   Found at " + i + " : " + widget);
                      }
                      return widget;
                  }
              }
              return null;
          }
  
          private boolean packageNeedsWidgetBackupLocked(String packageName, int userId) {
              int N = mWidgets.size();
              for (int i = 0; i < N; i++) {
                  Widget widget = mWidgets.get(i);
  
                  // Skip cross-user widgets.
                  if (!isProviderAndHostInUser(widget, userId)) {
                      continue;
                  }
  
                  if (widget.host.isInPackageForUser(packageName, userId)) {
                      // this package is hosting widgets, so it knows widget IDs.
                      return true;
                  }
  
                  Provider provider = widget.provider;
                  if (provider != null && provider.isInPackageForUser(packageName, userId)) {
                      // someone is hosting this app's widgets, so it knows widget IDs.
                      return true;
                  }
              }
              return false;
          }
  
          private void stashProviderRestoreUpdateLocked(Provider provider, int oldId, int newId) {
              ArrayList<RestoreUpdateRecord> r = mUpdatesByProvider.get(provider);
              if (r == null) {
                  r = new ArrayList<>();
                  mUpdatesByProvider.put(provider, r);
              } else {
                  // don't duplicate
                  if (alreadyStashed(r, oldId, newId)) {
                      if (DEBUG) {
                          Slog.i(TAG, "ID remap " + oldId + " -> " + newId
                                  + " already stashed for " + provider);
                      }
                      return;
                  }
              }
              r.add(new RestoreUpdateRecord(oldId, newId));
          }
  
          private boolean alreadyStashed(ArrayList<RestoreUpdateRecord> stash,
                  final int oldId, final int newId) {
              final int N = stash.size();
              for (int i = 0; i < N; i++) {
                  RestoreUpdateRecord r = stash.get(i);
                  if (r.oldId == oldId && r.newId == newId) {
                      return true;
                  }
              }
              return false;
          }
  
          private void stashHostRestoreUpdateLocked(Host host, int oldId, int newId) {
              ArrayList<RestoreUpdateRecord> r = mUpdatesByHost.get(host);
              if (r == null) {
                  r = new ArrayList<>();
                  mUpdatesByHost.put(host, r);
              } else {
                  if (alreadyStashed(r, oldId, newId)) {
                      if (DEBUG) {
                          Slog.i(TAG, "ID remap " + oldId + " -> " + newId
                                  + " already stashed for " + host);
                      }
                      return;
                  }
              }
              r.add(new RestoreUpdateRecord(oldId, newId));
          }
  
          private void sendWidgetRestoreBroadcastLocked(String action, Provider provider,
                  Host host, int[] oldIds, int[] newIds, UserHandle userHandle) {
              Intent intent = new Intent(action);
              intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS, oldIds);
              intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, newIds);
              if (provider != null) {
                  intent.setComponent(provider.info.provider);
                  sendBroadcastAsUser(intent, userHandle);
              }
              if (host != null) {
                  intent.setComponent(null);
                  intent.setPackage(host.id.packageName);
                  intent.putExtra(AppWidgetManager.EXTRA_HOST_ID, host.id.hostId);
                  sendBroadcastAsUser(intent, userHandle);
              }
          }
  
          // We're restoring widget state for 'pkg', so we start by wiping (a) all widget
          // instances that are hosted by that app, and (b) all instances in other hosts
          // for which 'pkg' is the provider.  We assume that we'll be restoring all of
          // these hosts & providers, so will be reconstructing a correct live state.
          private void pruneWidgetStateLocked(String pkg, int userId) {
              if (!mPrunedApps.contains(pkg)) {
                  if (DEBUG) {
                      Slog.i(TAG, "pruning widget state for restoring package " + pkg);
                  }
                  for (int i = mWidgets.size() - 1; i >= 0; i--) {
                      Widget widget = mWidgets.get(i);
  
                      Host host = widget.host;
                      Provider provider = widget.provider;
  
                      if (host.hostsPackageForUser(pkg, userId)
                              || (provider != null && provider.isInPackageForUser(pkg, userId))) {
                          // 'pkg' is either the host or the provider for this instances,
                          // so we tear it down in anticipation of it (possibly) being
                          // reconstructed due to the restore
                          host.widgets.remove(widget);
                          provider.widgets.remove(widget);
                          // Check if we need to destroy any services (if no other app widgets are
                          // referencing the same service)
                          decrementAppWidgetServiceRefCount(widget);
                          removeWidgetLocked(widget);
                      }
                  }
                  mPrunedApps.add(pkg);
              } else {
                  if (DEBUG) {
                      Slog.i(TAG, "already pruned " + pkg + ", continuing normally");
                  }
              }
          }
  
          private boolean isProviderAndHostInUser(Widget widget, int userId) {
              // Backup only widgets hosted or provided by the owner profile.
              return widget.host.getUserId() == userId && (widget.provider == null
                      || widget.provider.getUserId() == userId);
          }
  
          private Bundle parseWidgetIdOptions(XmlPullParser parser) {
              Bundle options = new Bundle();
              String minWidthString = parser.getAttributeValue(null, "min_width");
              if (minWidthString != null) {
                  options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH,
                          Integer.parseInt(minWidthString, 16));
              }
              String minHeightString = parser.getAttributeValue(null, "min_height");
              if (minHeightString != null) {
                  options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT,
                          Integer.parseInt(minHeightString, 16));
              }
              String maxWidthString = parser.getAttributeValue(null, "max_width");
              if (maxWidthString != null) {
                  options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH,
                          Integer.parseInt(maxWidthString, 16));
              }
              String maxHeightString = parser.getAttributeValue(null, "max_height");
              if (maxHeightString != null) {
                  options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT,
                          Integer.parseInt(maxHeightString, 16));
              }
              String categoryString = parser.getAttributeValue(null, "host_category");
              if (categoryString != null) {
                  options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
                          Integer.parseInt(categoryString, 16));
              }
              return options;
          }
  
          private int countPendingUpdates(ArrayList<RestoreUpdateRecord> updates) {
              int pending = 0;
              final int N = updates.size();
              for (int i = 0; i < N; i++) {
                  RestoreUpdateRecord r = updates.get(i);
                  if (!r.notified) {
                      pending++;
                  }
              }
              return pending;
          }
  
          // Accumulate a list of updates that affect the given provider for a final
          // coalesced notification broadcast once restore is over.
          private class RestoreUpdateRecord {
              public int oldId;
              public int newId;
              public boolean notified;
  
              public RestoreUpdateRecord(int theOldId, int theNewId) {
                  oldId = theOldId;
                  newId = theNewId;
                  notified = false;
              }
          }
      }
  
      private class AppWidgetManagerLocal extends AppWidgetManagerInternal {
          @Override
          public ArraySet<String> getHostedWidgetPackages(int uid) {
              synchronized (mLock) {
                  ArraySet<String> widgetPackages = null;
                  final int widgetCount = mWidgets.size();
                  for (int i = 0; i < widgetCount; i++) {
                      final Widget widget = mWidgets.get(i);
                      if  (widget.host.id.uid == uid) {
                          if (widgetPackages == null) {
                              widgetPackages = new ArraySet<>();
                          }
                          widgetPackages.add(widget.provider.id.componentName.getPackageName());
                      }
                  }
                  return widgetPackages;
              }
          }
  
          @Override
          public void unlockUser(int userId) {
              handleUserUnlocked(userId);
          }
  
      }
  }
  