content: /*
   * 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.ActivityTaskManager;
  import android.app.IActivityManager;
  import android.app.IActivityTaskManager;
  import android.app.ISearchManager;
  import android.app.SearchManager;
  import android.app.SearchableInfo;
  import android.content.ComponentName;
  import android.content.ContentResolver;
  import android.content.Context;
  import android.content.Intent;
  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.Handler;
  import android.os.RemoteException;
  import android.os.UserHandle;
  import android.os.UserManager;
  import android.provider.Settings;
  import android.service.voice.VoiceInteractionService;
  import android.util.Log;
  import android.util.SparseArray;
  
  import com.android.internal.annotations.GuardedBy;
  import com.android.internal.content.PackageMonitor;
  import com.android.internal.os.BackgroundThread;
  import com.android.internal.util.DumpUtils;
  import com.android.internal.util.IndentingPrintWriter;
  import com.android.server.LocalServices;
  import com.android.server.SystemService;
  import com.android.server.statusbar.StatusBarManagerInternal;
  
  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 {
      private static final String TAG = "SearchManagerService";
      final Handler mHandler;
  
      public static class Lifecycle extends SystemService {
          private SearchManagerService mService;
  
          public Lifecycle(Context context) {
              super(context);
          }
  
          @Override
          public void onStart() {
              mService = new SearchManagerService(getContext());
              publishBinderService(Context.SEARCH_SERVICE, mService);
          }
  
          @Override
          public void onUnlockUser(final int userId) {
              mService.mHandler.post(new Runnable() {
                  @Override
                  public void run() {
                      mService.onUnlockUser(userId);
                  }
              });
          }
  
          @Override
          public void onCleanupUser(int userHandle) {
              mService.onCleanupUser(userHandle);
          }
      }
  
      // Context that the service is running in.
      private final Context mContext;
  
      // This field is initialized lazily in getSearchables(), and then never modified.
      @GuardedBy("mSearchables")
      private final SparseArray<Searchables> mSearchables = new SparseArray<>();
  
      /**
       * 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;
          new MyPackageMonitor().register(context, null, UserHandle.ALL, true);
          new GlobalSearchProviderObserver(context.getContentResolver());
          mHandler = BackgroundThread.getHandler();
      }
  
      private Searchables getSearchables(int userId) {
          return getSearchables(userId, false);
      }
  
      private Searchables getSearchables(int userId, boolean forceUpdate) {
          final long token = Binder.clearCallingIdentity();
          try {
              final UserManager um = mContext.getSystemService(UserManager.class);
              if (um.getUserInfo(userId) == null) {
                  throw new IllegalStateException("User " + userId + " doesn't exist");
              }
              if (!um.isUserUnlockingOrUnlocked(userId)) {
                  throw new IllegalStateException("User " + userId + " isn't unlocked");
              }
          } finally {
              Binder.restoreCallingIdentity(token);
          }
          synchronized (mSearchables) {
              Searchables searchables = mSearchables.get(userId);
              if (searchables == null) {
                  searchables = new Searchables(mContext, userId);
                  searchables.updateSearchableList();
                  mSearchables.append(userId, searchables);
              } else if (forceUpdate) {
                  searchables.updateSearchableList();
              }
              return searchables;
          }
      }
  
      private void onUnlockUser(int userId) {
          try {
              getSearchables(userId, true);
          } catch (IllegalStateException ignored) {
              // We're just trying to warm a cache, so we don't mind if the user
              // was stopped or destroyed before we got here.
          }
      }
  
      private void onCleanupUser(int userId) {
          synchronized (mSearchables) {
              mSearchables.remove(userId);
          }
      }
  
      /**
       * 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)) {
                          mSearchables.valueAt(i).updateSearchableList();
                          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++) {
                      mSearchables.valueAt(i).updateSearchableList();
                  }
              }
              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.
       */
      @Override
      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.
       */
      @Override
      public List<SearchableInfo> getSearchablesInGlobalSearch() {
          return getSearchables(UserHandle.getCallingUserId()).getSearchablesInGlobalSearchList();
      }
  
      @Override
      public List<ResolveInfo> getGlobalSearchActivities() {
          return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivities();
      }
  
      /**
       * Gets the name of the global search activity.
       */
      @Override
      public ComponentName getGlobalSearchActivity() {
          return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivity();
      }
  
      /**
       * Gets the name of the web search activity.
       */
      @Override
      public ComponentName getWebSearchActivity() {
          return getSearchables(UserHandle.getCallingUserId()).getWebSearchActivity();
      }
  
      @Override
      public void launchAssist(Bundle args) {
          StatusBarManagerInternal statusBarManager =
                  LocalServices.getService(StatusBarManagerInternal.class);
          if (statusBarManager != null) {
              statusBarManager.startAssist(args);
          }
      }
  
      // Check and return VIS component
      private ComponentName getLegacyAssistComponent(int userHandle) {
          try {
              userHandle = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                      Binder.getCallingUid(), userHandle, true, false, "getLegacyAssistComponent",
                      null);
              PackageManager pm = mContext.getPackageManager();
              Intent intentAssistProbe = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
              List<ResolveInfo> infoListVis = pm.queryIntentServicesAsUser(intentAssistProbe,
                      PackageManager.MATCH_SYSTEM_ONLY, userHandle);
              if (infoListVis == null || infoListVis.isEmpty()) {
                  return null;
              } else {
                  ResolveInfo rInfo = infoListVis.get(0);
                  return new ComponentName(
                          rInfo.serviceInfo.applicationInfo.packageName,
                          rInfo.serviceInfo.name);
  
              }
          } catch (Exception e) {
              Log.e(TAG, "Exception in getLegacyAssistComponent: " + e);
          }
          return null;
      }
  
      @Override
      public boolean launchLegacyAssist(String hint, int userHandle, Bundle args) {
          ComponentName comp = getLegacyAssistComponent(userHandle);
          if (comp == null) {
              return false;
          }
          long ident = Binder.clearCallingIdentity();
          try {
              Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
              intent.setComponent(comp);
  
              IActivityTaskManager am = ActivityTaskManager.getService();
              if (args != null) {
                  args.putInt(Intent.EXTRA_KEY_EVENT, android.view.KeyEvent.KEYCODE_ASSIST);
              }
              intent.putExtras(args);
  
              return am.launchAssistIntent(intent, ActivityManager.ASSIST_CONTEXT_BASIC, hint,
                      userHandle, args);
          } catch (RemoteException e) {
          } finally {
              Binder.restoreCallingIdentity(ident);
          }
          return true;
      }
  
      @Override
      public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
          if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
  
          IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
          synchronized (mSearchables) {
              for (int i = 0; i < mSearchables.size(); i++) {
                  ipw.print("\nUser: "); ipw.println(mSearchables.keyAt(i));
                  ipw.increaseIndent();
                  mSearchables.valueAt(i).dump(fd, ipw, args);
                  ipw.decreaseIndent();
              }
          }
      }
  }
  