gt:         synchronized (mSearchables) {
context: /*
   * Copyright (C) 2007 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.search;
  
  import android.app.ActivityManager;
  import android.app.ActivityManagerNative;
  import android.app.AppGlobals;
  import android.app.IActivityManager;
  import android.app.ISearchManager;
  import android.app.SearchManager;
  import android.app.SearchableInfo;
  import android.content.BroadcastReceiver;
  import android.content.ComponentName;
  import android.content.ContentResolver;
  import android.content.Context;
  import android.content.Intent;
  import android.content.IntentFilter;
  import android.content.pm.IPackageManager;
  import android.content.pm.PackageManager;
  import android.content.pm.ResolveInfo;
  import android.database.ContentObserver;
  import android.os.Binder;
  import android.os.Bundle;
  import android.os.Process;
  import android.os.RemoteException;
  import android.os.UserHandle;
  import android.os.UserManager;
  import android.provider.Settings;
  import android.util.Log;
  import android.util.SparseArray;
  
  import com.android.internal.content.PackageMonitor;
  import com.android.internal.util.IndentingPrintWriter;
  
  import java.io.FileDescriptor;
  import java.io.PrintWriter;
  import java.util.List;
  
  /**
   * The search manager service handles the search UI, and maintains a registry of searchable
   * activities.
   */
  public class SearchManagerService extends ISearchManager.Stub {
  
      // general debugging support
      private static final String TAG = "SearchManagerService";
  
      // Context that the service is running in.
      private final Context mContext;
  
      // This field is initialized lazily in getSearchables(), and then never modified.
      private final SparseArray<Searchables> mSearchables = new SparseArray<Searchables>();
  
      /**
       * Initializes the Search Manager service in the provided system context.
       * Only one instance of this object should be created!
       *
       * @param context to use for accessing DB, window manager, etc.
       */
      public SearchManagerService(Context context)  {
          mContext = context;
          IntentFilter filter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
          filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
          mContext.registerReceiver(new BootCompletedReceiver(), filter);
          mContext.registerReceiver(new UserReceiver(),
                  new IntentFilter(Intent.ACTION_USER_REMOVED));
          new MyPackageMonitor().register(context, null, UserHandle.ALL, true);
      }
  
      private Searchables getSearchables(int userId) {
          long origId = Binder.clearCallingIdentity();
          try {
              boolean userExists = ((UserManager) mContext.getSystemService(Context.USER_SERVICE))
                      .getUserInfo(userId) != null;
              if (!userExists) return null;
          } finally {
              Binder.restoreCallingIdentity(origId);
          }
          synchronized (mSearchables) {
              Searchables searchables = mSearchables.get(userId);
  
              if (searchables == null) {
                  //Log.i(TAG, "Building list of searchable activities for userId=" + userId);
                  searchables = new Searchables(mContext, userId);
                  searchables.buildSearchableList();
                  mSearchables.append(userId, searchables);
              }
              return searchables;
          }
      }
  
      private void onUserRemoved(int userId) {
          if (userId != UserHandle.USER_OWNER) {
              synchronized (mSearchables) {
                  mSearchables.remove(userId);
              }
          }
      }
  
      /**
       * Creates the initial searchables list after boot.
       */
      private final class BootCompletedReceiver extends BroadcastReceiver {
          @Override
          public void onReceive(Context context, Intent intent) {
              new Thread() {
                  @Override
                  public void run() {
                      Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                      mContext.unregisterReceiver(BootCompletedReceiver.this);
                      getSearchables(0);
                  }
              }.start();
          }
      }
  
      private final class UserReceiver extends BroadcastReceiver {
          @Override
          public void onReceive(Context context, Intent intent) {
              onUserRemoved(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_OWNER));
          }
      }
  
      /**
       * Refreshes the "searchables" list when packages are added/removed.
       */
      class MyPackageMonitor extends PackageMonitor {
  
          @Override
          public void onSomePackagesChanged() {
              updateSearchables();
          }
  
          @Override
          public void onPackageModified(String pkg) {
              updateSearchables();
          }
  
          private void updateSearchables() {
              final int changingUserId = getChangingUserId();
              synchronized (mSearchables) {
                  // Update list of searchable activities
                  for (int i = 0; i < mSearchables.size(); i++) {
                      if (changingUserId == mSearchables.keyAt(i)) {
                          getSearchables(mSearchables.keyAt(i)).buildSearchableList();
                          break;
                      }
                  }
              }
              // Inform all listeners that the list of searchables has been updated.
              Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
              intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
                      | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
              mContext.sendBroadcastAsUser(intent, new UserHandle(changingUserId));
          }
      }
  
      class GlobalSearchProviderObserver extends ContentObserver {
          private final ContentResolver mResolver;
  
          public GlobalSearchProviderObserver(ContentResolver resolver) {
              super(null);
              mResolver = resolver;
              mResolver.registerContentObserver(
                      Settings.Secure.getUriFor(Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY),
                      false /* notifyDescendants */,
                      this);
          }
  
          @Override
          public void onChange(boolean selfChange) {
              synchronized (mSearchables) {
                  for (int i = 0; i < mSearchables.size(); i++) {
                      getSearchables(mSearchables.keyAt(i)).buildSearchableList();
                  }
              }
              Intent intent = new Intent(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
              intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
              mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
          }
  
      }
  
      //
      // Searchable activities API
      //
  
      /**
       * Returns the SearchableInfo for a given activity.
       *
       * @param launchActivity The activity from which we're launching this search.
       * @return Returns a SearchableInfo record describing the parameters of the search,
       * or null if no searchable metadata was available.
       */
      public SearchableInfo getSearchableInfo(final ComponentName launchActivity) {
          if (launchActivity == null) {
              Log.e(TAG, "getSearchableInfo(), activity == null");
              return null;
          }
          return getSearchables(UserHandle.getCallingUserId()).getSearchableInfo(launchActivity);
      }
  
      /**
       * Returns a list of the searchable activities that can be included in global search.
       */
      public List<SearchableInfo> getSearchablesInGlobalSearch() {
          return getSearchables(UserHandle.getCallingUserId()).getSearchablesInGlobalSearchList();
      }
  
      public List<ResolveInfo> getGlobalSearchActivities() {
          return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivities();
      }
  
      /**
       * Gets the name of the global search activity.
       */
      public ComponentName getGlobalSearchActivity() {
          return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivity();
      }
  
      /**
       * Gets the name of the web search activity.
       */
      public ComponentName getWebSearchActivity() {
          return getSearchables(UserHandle.getCallingUserId()).getWebSearchActivity();
      }
  
      @Override
      public ComponentName getAssistIntent(int userHandle) {
          try {
              userHandle = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                      Binder.getCallingUid(), userHandle, true, false, "getAssistIntent", null);
              IPackageManager pm = AppGlobals.getPackageManager();
              Intent assistIntent = new Intent(Intent.ACTION_ASSIST);
              ResolveInfo info =
                      pm.resolveIntent(assistIntent,
                      assistIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
                      PackageManager.MATCH_DEFAULT_ONLY, userHandle);
              if (info != null) {
                  return new ComponentName(
                          info.activityInfo.applicationInfo.packageName,
                          info.activityInfo.name);
              }
          } catch (RemoteException re) {
              // Local call
              Log.e(TAG, "RemoteException in getAssistIntent: " + re);
          } catch (Exception e) {
              Log.e(TAG, "Exception in getAssistIntent: " + e);
          }
          return null;
      }
  
      @Override
      public boolean launchAssistAction(int requestType, String hint, int userHandle) {
          ComponentName comp = getAssistIntent(userHandle);
          if (comp == null) {
              return false;
          }
          long ident = Binder.clearCallingIdentity();
          try {
              Intent intent = new Intent(Intent.ACTION_ASSIST);
              intent.setComponent(comp);
              IActivityManager am = ActivityManagerNative.getDefault();
              return am.launchAssistIntent(intent, requestType, hint, userHandle);
          } catch (RemoteException e) {
          } finally {
              Binder.restoreCallingIdentity(ident);
          }
          return true;
      }
  
      @Override
      public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
          mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
  
          IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
  