content: /*
   * Copyright (C) 2018 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.internal.telephony.ims;
  
  import android.content.ComponentName;
  import android.content.Context;
  import android.content.Intent;
  import android.content.ServiceConnection;
  import android.os.IBinder;
  import android.telephony.ims.aidl.IImsServiceController;
  import android.telephony.ims.stub.ImsFeatureConfiguration;
  import android.util.Log;
  
  import java.util.HashMap;
  import java.util.Map;
  import java.util.Set;
  
  /**
   * Manages the querying of multiple ImsServices asynchronously in order to retrieve the ImsFeatures
   * they support.
   */
  
  public class ImsServiceFeatureQueryManager {
  
      private final class ImsServiceFeatureQuery implements ServiceConnection {
  
          private static final String LOG_TAG = "ImsServiceFeatureQuery";
  
          private final ComponentName mName;
          private final String mIntentFilter;
  
          ImsServiceFeatureQuery(ComponentName name, String intentFilter) {
              mName = name;
              mIntentFilter = intentFilter;
          }
  
          /**
           * Starts the bind to the ImsService specified ComponentName.
           * @return true if binding started, false if it failed and will not recover.
           */
          public boolean start() {
              Log.d(LOG_TAG, "start: intent filter=" + mIntentFilter + ", name=" + mName);
              Intent imsServiceIntent = new Intent(mIntentFilter).setComponent(mName);
              int serviceFlags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
                      | Context.BIND_IMPORTANT;
              boolean bindStarted = mContext.bindService(imsServiceIntent, this, serviceFlags);
              if (!bindStarted) {
                  // Docs say to unbind if this fails.
                  cleanup();
              }
              return bindStarted;
          }
  
          @Override
          public void onServiceConnected(ComponentName name, IBinder service) {
              Log.i(LOG_TAG, "onServiceConnected for component: " + name);
              if (service != null) {
                  queryImsFeatures(IImsServiceController.Stub.asInterface(service));
              } else {
                  Log.w(LOG_TAG, "onServiceConnected: " + name + " binder null, cleaning up.");
                  cleanup();
              }
          }
  
          @Override
          public void onServiceDisconnected(ComponentName name) {
              Log.w(LOG_TAG, "onServiceDisconnected for component: " + name);
          }
  
          private void queryImsFeatures(IImsServiceController controller) {
              ImsFeatureConfiguration config;
              try {
                  config = controller.querySupportedImsFeatures();
              } catch (Exception e) {
                  Log.w(LOG_TAG, "queryImsFeatures - error: " + e);
                  cleanup();
                  mListener.onError(mName);
                  return;
              }
              Set<ImsFeatureConfiguration.FeatureSlotPair> servicePairs = config.getServiceFeatures();
              // Complete, remove from active queries and notify.
              cleanup();
              mListener.onComplete(mName, servicePairs);
          }
  
          private void cleanup() {
              mContext.unbindService(this);
              synchronized (mLock) {
                  mActiveQueries.remove(mName);
              }
          }
      }
  
      public interface Listener {
          /**
           * Called when a query has completed.
           * @param name The Package Name of the query
           * @param features A Set of slotid->feature pairs that the ImsService supports.
           */
          void onComplete(ComponentName name, Set<ImsFeatureConfiguration.FeatureSlotPair> features);
  
          /**
           * Called when a query has failed and should be retried.
           */
          void onError(ComponentName name);
      }
  
      // Maps an active ImsService query (by Package Name String) its query.
      private final Map<ComponentName, ImsServiceFeatureQuery> mActiveQueries = new HashMap<>();
      private final Context mContext;
      private final Listener mListener;
      private final Object mLock = new Object();
  
      public ImsServiceFeatureQueryManager(Context context, Listener listener) {
          mContext = context;
          mListener = listener;
      }
  
      /**
       * Starts an ImsService feature query for the ComponentName and Intent specified.
       * @param name The ComponentName of the ImsService being queried.
       * @param intentFilter The Intent filter that the ImsService specified.
       * @return true if the query started, false if it was unable to start.
       */
      public boolean startQuery(ComponentName name, String intentFilter) {
          synchronized (mLock) {
              if (mActiveQueries.containsKey(name)) {
                  // We already have an active query, wait for it to return.
                  return true;
              }
              ImsServiceFeatureQuery query = new ImsServiceFeatureQuery(name, intentFilter);
              mActiveQueries.put(name, query);
              return query.start();
          }
      }
  
      /**
       * @return true if there are any active queries, false if the manager is idle.
       */
      public boolean isQueryInProgress() {
          synchronized (mLock) {
              return !mActiveQueries.isEmpty();
          }
      }
  }
  