/**
   * Blocks current thread until subject object is released. It should be used to block UI thread
   * before the <tt>View</tt> is hidden.
   */
  public synchronized void waitForObjectRelease() {
    if (providedObject != null) {
      try {
        logger.info("Waiting for object release... " + hashCode());
        this.wait(REMOVAL_TIMEOUT);
        if (providedObject != null) {
          throw new RuntimeException("Timeout waiting for preview surface removal");
        }
        logger.info("Object released! " + hashCode());
      } catch (InterruptedException e) {
        throw new RuntimeException(e);
      }
    }

    ensureViewDestroyed();
  }
 /**
  * Should be called by consumer to obtain the object. It is causing hidden <tt>View</tt> to be
  * displayed and eventually {@link #onObjectCreated(Object)} method to be called which results in
  * object creation.
  *
  * @return provided object.
  */
 public synchronized T obtainObject() {
   ensureViewCreated();
   if (this.providedObject == null) {
     try {
       logger.info("Waiting for object..." + hashCode());
       this.wait(CREATE_TIMEOUT);
       if (providedObject == null) {
         throw new RuntimeException("Timeout waiting for surface");
       }
       logger.info("Returning object! " + hashCode());
     } catch (InterruptedException e) {
       throw new RuntimeException(e);
     }
   }
   return providedObject;
 }
  /**
   * Creates new call to target <tt>destination</tt>.
   *
   * @param context the android context
   * @param destination the target callee name that will be used.
   */
  private static void createCall(Context context, String destination) {
    Iterator<ProtocolProviderService> allProviders =
        AccountUtils.getRegisteredProviders().iterator();

    if (!allProviders.hasNext()) {
      logger.error("No registered providers found");
      return;
    }

    createCall(context, destination, allProviders.next());
  }
  /**
   * Checks if there is a call in progress. If true then shows a warning toast and finishes the
   * activity.
   *
   * @param activity activity doing a check.
   * @return <tt>true</tt> if there is call in progress and <tt>Activity</tt> was finished.
   */
  public static boolean checkCallInProgress(Activity activity) {
    if (CallManager.getActiveCallsCount() > 0) {
      logger.warn("Call is in progress");

      Toast t =
          Toast.makeText(activity, R.string.service_gui_WARN_CALL_IN_PROGRESS, Toast.LENGTH_SHORT);
      t.show();

      activity.finish();
      return true;
    } else {
      return false;
    }
  }
  /**
   * Creates new call to given <tt>destination</tt> using selected <tt>provider</tt>.
   *
   * @param context the android context
   * @param destination target callee name.
   * @param provider the provider that will be used to make a call.
   */
  public static void createCall(
      final Context context, final String destination, final ProtocolProviderService provider) {
    if (createCallThread != null) {
      logger.warn("Another call is already being created");
      return;
    } else if (CallManager.getActiveCallsCount() > 0) {
      logger.warn("Another call is in progress");
      return;
    }

    final long dialogId =
        ProgressDialogFragment.showProgressDialog(
            JitsiApplication.getResString(R.string.service_gui_OUTGOING_CALL),
            JitsiApplication.getResString(R.string.service_gui_OUTGOING_CALL_MSG, destination));

    createCallThread =
        new Thread("Create call thread") {
          public void run() {
            try {
              CallManager.createCall(provider, destination);
            } catch (Throwable t) {
              logger.error("Error creating the call: " + t.getMessage(), t);
              AndroidUtils.showAlertDialog(
                  context, context.getString(R.string.service_gui_ERROR), t.getMessage());
            } finally {
              if (DialogActivity.waitForDialogOpened(dialogId)) {
                DialogActivity.closeDialog(JitsiApplication.getGlobalContext(), dialogId);
              } else {
                logger.error("Failed to wait for the dialog: " + dialogId);
              }
              createCallThread = null;
            }
          }
        };

    createCallThread.start();
  }
/**
 * @author Yana Stamcheva
 * @author Pawel Domas
 */
public class AndroidCallUtil {
  /** The logger for this class. */
  private static final Logger logger = Logger.getLogger(AndroidCallUtil.class);

  /** Field used to track the thread used to create outgoing calls. */
  private static Thread createCallThread;

  /**
   * Creates an android call.
   *
   * @param context the android context
   * @param callButtonView the button view that generated the call
   * @param contact the contact address to call
   */
  public static void createAndroidCall(Context context, View callButtonView, String contact) {
    if (AccountUtils.getRegisteredProviders().size() > 1)
      showCallViaMenu(context, callButtonView, contact);
    else createCall(context, contact);
  }

  /**
   * Creates new call to target <tt>destination</tt>.
   *
   * @param context the android context
   * @param destination the target callee name that will be used.
   */
  private static void createCall(Context context, String destination) {
    Iterator<ProtocolProviderService> allProviders =
        AccountUtils.getRegisteredProviders().iterator();

    if (!allProviders.hasNext()) {
      logger.error("No registered providers found");
      return;
    }

    createCall(context, destination, allProviders.next());
  }

  /**
   * Creates new call to given <tt>destination</tt> using selected <tt>provider</tt>.
   *
   * @param context the android context
   * @param destination target callee name.
   * @param provider the provider that will be used to make a call.
   */
  public static void createCall(
      final Context context, final String destination, final ProtocolProviderService provider) {
    if (createCallThread != null) {
      logger.warn("Another call is already being created");
      return;
    } else if (CallManager.getActiveCallsCount() > 0) {
      logger.warn("Another call is in progress");
      return;
    }

    final long dialogId =
        ProgressDialogFragment.showProgressDialog(
            JitsiApplication.getResString(R.string.service_gui_OUTGOING_CALL),
            JitsiApplication.getResString(R.string.service_gui_OUTGOING_CALL_MSG, destination));

    createCallThread =
        new Thread("Create call thread") {
          public void run() {
            try {
              CallManager.createCall(provider, destination);
            } catch (Throwable t) {
              logger.error("Error creating the call: " + t.getMessage(), t);
              AndroidUtils.showAlertDialog(
                  context, context.getString(R.string.service_gui_ERROR), t.getMessage());
            } finally {
              if (DialogActivity.waitForDialogOpened(dialogId)) {
                DialogActivity.closeDialog(JitsiApplication.getGlobalContext(), dialogId);
              } else {
                logger.error("Failed to wait for the dialog: " + dialogId);
              }
              createCallThread = null;
            }
          }
        };

    createCallThread.start();
  }

  /**
   * Shows "call via" menu allowing user to selected from multiple providers.
   *
   * @param context the android context
   * @param v the View that will contain the popup menu.
   * @param destination target callee name.
   */
  private static void showCallViaMenu(final Context context, View v, final String destination) {
    PopupMenu popup = new PopupMenu(context, v);

    Menu menu = popup.getMenu();

    Iterator<ProtocolProviderService> registeredProviders =
        AccountUtils.getRegisteredProviders().iterator();

    while (registeredProviders.hasNext()) {
      final ProtocolProviderService provider = registeredProviders.next();
      String accountAddress = provider.getAccountID().getAccountAddress();

      MenuItem menuItem = menu.add(Menu.NONE, Menu.NONE, Menu.NONE, accountAddress);

      menuItem.setOnMenuItemClickListener(
          new MenuItem.OnMenuItemClickListener() {
            public boolean onMenuItemClick(MenuItem item) {
              createCall(context, destination, provider);

              return false;
            }
          });
    }

    popup.show();
  }

  /**
   * Checks if there is a call in progress. If true then shows a warning toast and finishes the
   * activity.
   *
   * @param activity activity doing a check.
   * @return <tt>true</tt> if there is call in progress and <tt>Activity</tt> was finished.
   */
  public static boolean checkCallInProgress(Activity activity) {
    if (CallManager.getActiveCallsCount() > 0) {
      logger.warn("Call is in progress");

      Toast t =
          Toast.makeText(activity, R.string.service_gui_WARN_CALL_IN_PROGRESS, Toast.LENGTH_SHORT);
      t.show();

      activity.finish();
      return true;
    } else {
      return false;
    }
  }
}
/**
 * <tt>ViewDependentProvider</tt> is used to implement classes that provide objects dependent on
 * <tt>View</tt> visibility state. It means that they can provide it only when <tt>View</tt> is
 * visible and they have to release such object before <tt>View</tt> is hidden.
 *
 * @author Pawel Domas
 */
public abstract class ViewDependentProvider<T> {
  /** The logger */
  private static final Logger logger = Logger.getLogger(ViewDependentProvider.class);

  /** Timeout for dispose surface operation */
  private static final long REMOVAL_TIMEOUT = 10000L;

  /** Timeout for create surface operation */
  private static final long CREATE_TIMEOUT = 10000L;

  /** <tt>Activity</tt> context. */
  protected final Activity activity;

  /** The container that will hold maintained view. */
  private final ViewGroup container;

  /** The view maintained by this instance. */
  protected View view;

  /** Provided object created when <tt>View</tt> is visible. */
  protected T providedObject;

  /**
   * Creates new instance of <tt>ViewDependentProvider</tt>.
   *
   * @param activity parent <tt>Activity</tt> that manages the <tt>container</tt>.
   * @param container the container that will hold maintained <tt>View</tt>.
   */
  public ViewDependentProvider(Activity activity, ViewGroup container) {
    this.activity = activity;
    this.container = container;
  }

  /**
   * Checks if the view is currently created. If not creates new <tt>View</tt> and adds it to the
   * <tt>container</tt>.
   */
  protected void ensureViewCreated() {
    if (view == null) {
      activity.runOnUiThread(
          new Runnable() {
            @Override
            public void run() {
              view = createViewInstance();

              ViewGroup.LayoutParams params =
                  new ViewGroup.LayoutParams(
                      ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
              container.addView(view, params);

              container.setVisibility(View.VISIBLE);
            }
          });
    }
  }

  /**
   * Factory method that creates new <tt>View</tt> instance.
   *
   * @return new <tt>View</tt> instance.
   */
  protected abstract View createViewInstance();

  /** Checks if maintained view exists and removes if from the <tt>container</tt>. */
  protected void ensureViewDestroyed() {
    if (view != null) {
      final View viewToRemove = view;
      view = null;

      activity.runOnUiThread(
          new Runnable() {
            @Override
            public void run() {
              container.removeView(viewToRemove);
              container.setVisibility(View.GONE);
            }
          });
    }
  }

  /**
   * Must be called by subclasses when provided object is created.
   *
   * @param obj provided object instance.
   */
  protected synchronized void onObjectCreated(T obj) {
    this.providedObject = obj;
    this.notifyAll();
  }

  /**
   * Should be called by consumer to obtain the object. It is causing hidden <tt>View</tt> to be
   * displayed and eventually {@link #onObjectCreated(Object)} method to be called which results in
   * object creation.
   *
   * @return provided object.
   */
  public synchronized T obtainObject() {
    ensureViewCreated();
    if (this.providedObject == null) {
      try {
        logger.info("Waiting for object..." + hashCode());
        this.wait(CREATE_TIMEOUT);
        if (providedObject == null) {
          throw new RuntimeException("Timeout waiting for surface");
        }
        logger.info("Returning object! " + hashCode());
      } catch (InterruptedException e) {
        throw new RuntimeException(e);
      }
    }
    return providedObject;
  }

  /**
   * Checks if provider has already the object and returns it immediately. If there is no object and
   * we would have to wait for it, then the <tt>null</tt> is returned.
   *
   * @return the object if it is currently held by this provider or <tt>null</tt> otherwise.
   */
  public synchronized T tryObtainObject() {
    return providedObject;
  }

  /** Should be called by subclasses when object is destroyed. */
  protected synchronized void onObjectDestroyed() {
    releaseObject();
  }

  /** Should be called by the consumer to release the object. */
  public void onObjectReleased() {
    releaseObject();
    // Remove the view once it's released
    ensureViewDestroyed();
  }

  /** Releases the subject object and notifies all threads waiting on the lock. */
  protected synchronized void releaseObject() {
    if (providedObject == null) return;

    this.providedObject = null;
    this.notifyAll();
  }

  /**
   * Blocks current thread until subject object is released. It should be used to block UI thread
   * before the <tt>View</tt> is hidden.
   */
  public synchronized void waitForObjectRelease() {
    if (providedObject != null) {
      try {
        logger.info("Waiting for object release... " + hashCode());
        this.wait(REMOVAL_TIMEOUT);
        if (providedObject != null) {
          throw new RuntimeException("Timeout waiting for preview surface removal");
        }
        logger.info("Object released! " + hashCode());
      } catch (InterruptedException e) {
        throw new RuntimeException(e);
      }
    }

    ensureViewDestroyed();
  }
}