content: package io.agora.openacall.ui;
  
  import android.content.Intent;
  import android.graphics.PorterDuff;
  import android.media.AudioManager;
  import android.os.Bundle;
  import android.os.Handler;
  import android.os.Message;
  import android.view.Menu;
  import android.view.MenuItem;
  import android.view.View;
  import android.view.WindowManager;
  import android.widget.EditText;
  import android.widget.FrameLayout;
  import android.widget.ImageView;
  import android.widget.LinearLayout;
  import android.widget.TextView;
  
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import io.agora.openacall.R;
  import io.agora.openacall.model.AGEventHandler;
  import io.agora.openacall.model.ConstantApp;
  import io.agora.rtc.IRtcEngineEventHandler;
  import io.agora.rtc.RtcEngine;
  
  public class ChatActivity extends BaseActivity implements AGEventHandler {
  
      private final static Logger log = LoggerFactory.getLogger(ChatActivity.class);
  
      private volatile boolean mAudioMuted = false;
  
      private volatile int mAudioRouting = -1; // Default
  
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_chat);
      }
  
      @Override
      public boolean onCreateOptionsMenu(Menu menu) {
          return false;
      }
  
      @Override
      public boolean onOptionsItemSelected(MenuItem item) {
          return false;
      }
  
      @Override
      protected void initUIandEvent() {
          event().addEventHandler(this);
  
          Intent i = getIntent();
  
          String channelName = i.getStringExtra(ConstantApp.ACTION_KEY_CHANNEL_NAME);
  
          /*
            Allows a user to join a channel.
  
            Users in the same channel can talk to each other, and multiple users in the same channel can start a group chat. Users with different App IDs cannot call each other.
  
            You must call the leaveChannel method to exit the current call before joining another channel.
  
            A successful joinChannel method call triggers the following callbacks:
  
            The local client: onJoinChannelSuccess.
            The remote client: onUserJoined, if the user joining the channel is in the Communication profile, or is a BROADCASTER in the Live Broadcast profile.
  
            When the connection between the client and Agora's server is interrupted due to poor
            network conditions, the SDK tries reconnecting to the server. When the local client
            successfully rejoins the channel, the SDK triggers the onRejoinChannelSuccess callback
            on the local client.
  
           */
          worker().joinChannel(channelName, config().mUid);
  
          TextView textChannelName = (TextView) findViewById(R.id.channel_name);
          textChannelName.setText(channelName);
  
          optional();
  
          LinearLayout bottomContainer = (LinearLayout) findViewById(R.id.bottom_container);
          FrameLayout.MarginLayoutParams fmp = (FrameLayout.MarginLayoutParams) bottomContainer.getLayoutParams();
          fmp.bottomMargin = virtualKeyHeight() + 16;
      }
  
      private Handler mMainHandler;
  
      private static final int UPDATE_UI_MESSAGE = 0x1024;
  
      EditText mMessageList;
  
      StringBuffer mMessageCache = new StringBuffer();
  
      private void notifyMessageChanged(String msg) {
          if (mMessageCache.length() > 10000) { // drop messages
              mMessageCache = new StringBuffer(mMessageCache.substring(10000 - 40));
          }
  
          mMessageCache.append(System.currentTimeMillis()).append(": ").append(msg).append("\n"); // append timestamp for messages
  
          runOnUiThread(new Runnable() {
              @Override
              public void run() {
                  if (isFinishing()) {
                      return;
                  }
  
                  if (mMainHandler == null) {
                      mMainHandler = new Handler(getMainLooper()) {
                          @Override
                          public void handleMessage(Message msg) {
                              super.handleMessage(msg);
  
                              if (isFinishing()) {
                                  return;
                              }
  
                              switch (msg.what) {
                                  case UPDATE_UI_MESSAGE:
                                      String content = (String) (msg.obj);
                                      mMessageList.setText(content);
                                      mMessageList.setSelection(content.length());
                                      break;
  
                                  default:
                                      break;
                              }
  
                          }
                      };
  
                      mMessageList = (EditText) findViewById(R.id.msg_list);
                  }
  
                  mMainHandler.removeMessages(UPDATE_UI_MESSAGE);
                  Message envelop = new Message();
                  envelop.what = UPDATE_UI_MESSAGE;
                  envelop.obj = mMessageCache.toString();
                  mMainHandler.sendMessageDelayed(envelop, 1000l);
              }
          });
      }
  
      private void optional() {
          getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN);
          getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
  
          setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
      }
  
      private void optionalDestroy() {
      }
  
      public void onSwitchSpeakerClicked(View view) {
          log.info("onSwitchSpeakerClicked " + view + " " + mAudioMuted + " " + mAudioRouting);
  
          RtcEngine rtcEngine = rtcEngine();
  
          /*
            Enables/Disables the audio playback route to the speakerphone.
            This method sets whether the audio is routed to the speakerphone or earpiece.
            After calling this method, the SDK returns the onAudioRouteChanged callback
            to indicate the changes.
           */
          rtcEngine.setEnableSpeakerphone(mAudioRouting != 3);
      }
  
      @Override
      protected void deInitUIandEvent() {
          optionalDestroy();
  
          doLeaveChannel();
          event().removeEventHandler(this);
      }
  
      /**
       * Allows a user to leave a channel.
       *
       * After joining a channel, the user must call the leaveChannel method to end the call before
       * joining another channel. This method returns 0 if the user leaves the channel and releases
       * all resources related to the call. This method call is asynchronous, and the user has not
       * exited the channel when the method call returns. Once the user leaves the channel,
       * the SDK triggers the onLeaveChannel callback.
       *
       * A successful leaveChannel method call triggers the following callbacks:
       *
       * The local client: onLeaveChannel.
       * The remote client: onUserOffline, if the user leaving the channel is in the
       * Communication channel, or is a BROADCASTER in the Live Broadcast profile.
       *
       */
      private void doLeaveChannel() {
          worker().leaveChannel(config().mChannel);
      }
  
      public void onEndCallClicked(View view) {
          log.info("onEndCallClicked " + view);
  
          quitCall();
      }
  
      @Override
      public void onBackPressed() {
          super.onBackPressed();
  
          log.info("onBackPressed");
  
          quitCall();
      }
  
      private void quitCall() {
          Intent intent = new Intent(this, MainActivity.class);
          startActivity(intent);
  
          finish();
      }
  
      public void onVoiceMuteClicked(View view) {
          log.info("onVoiceMuteClicked " + view + " audio_status: " + mAudioMuted);
  
          RtcEngine rtcEngine = rtcEngine();
          rtcEngine.muteLocalAudioStream(mAudioMuted = !mAudioMuted);
  
          ImageView iv = (ImageView) view;
  
          if (mAudioMuted) {
              iv.setColorFilter(getResources().getColor(R.color.agora_blue), PorterDuff.Mode.MULTIPLY);
          } else {
              iv.clearColorFilter();
          }
      }
  
      @Override
      public void onJoinChannelSuccess(String channel, final int uid, int elapsed) {
          String msg = "onJoinChannelSuccess " + channel + " " + (uid & 0xFFFFFFFFL) + " " + elapsed;
          log.debug(msg);
  
          notifyMessageChanged(msg);
  
          runOnUiThread(new Runnable() {
              @Override
              public void run() {
                  if (isFinishing()) {
                      return;
                  }
  
                  rtcEngine().muteLocalAudioStream(mAudioMuted);
              }
          });
      }
  
      @Override
      public void onUserOffline(int uid, int reason) {
          String msg = "onUserOffline " + (uid & 0xFFFFFFFFL) + " " + reason;
          log.debug(msg);
  
          notifyMessageChanged(msg);
  
      }
  
      @Override
      public void onExtraCallback(final int type, final Object... data) {
  
          runOnUiThread(new Runnable() {
              @Override
              public void run() {
                  if (isFinishing()) {
                      return;
                  }
  
                  doHandleExtraCallback(type, data);
              }
          });
      }
  
      private void doHandleExtraCallback(int type, Object... data) {
          int peerUid;
          boolean muted;
  
          switch (type) {
              case AGEventHandler.EVENT_TYPE_ON_USER_AUDIO_MUTED: {
                  peerUid = (Integer) data[0];
                  muted = (boolean) data[1];
  
                  notifyMessageChanged("mute: " + (peerUid & 0xFFFFFFFFL) + " " + muted);
                  break;
              }
  
              case AGEventHandler.EVENT_TYPE_ON_AUDIO_QUALITY: {
                  peerUid = (Integer) data[0];
                  int quality = (int) data[1];
                  short delay = (short) data[2];
                  short lost = (short) data[3];
  
                  notifyMessageChanged("quality: " + (peerUid & 0xFFFFFFFFL) + " " + quality + " " + delay + " " + lost);
                  break;
              }
  
              case AGEventHandler.EVENT_TYPE_ON_SPEAKER_STATS: {
                  IRtcEngineEventHandler.AudioVolumeInfo[] infos = (IRtcEngineEventHandler.AudioVolumeInfo[]) data[0];
  
                  if (infos.length == 1 && infos[0].uid == 0) { // local guy, ignore it
                      break;
                  }
  
                  StringBuilder volumeCache = new StringBuilder();
                  for (IRtcEngineEventHandler.AudioVolumeInfo each : infos) {
                      peerUid = each.uid;
                      int peerVolume = each.volume;
  
                      if (peerUid == 0) {
                          continue;
                      }
  
                      volumeCache.append("volume: ").append(peerUid & 0xFFFFFFFFL).append(" ").append(peerVolume).append("\n");
                  }
  
                  if (volumeCache.length() > 0) {
                      String volumeMsg = volumeCache.substring(0, volumeCache.length() - 1);
                      notifyMessageChanged(volumeMsg);
  
                      if ((System.currentTimeMillis() / 1000) % 10 == 0) {
                          log.debug(volumeMsg);
                      }
                  }
                  break;
              }
  
              case AGEventHandler.EVENT_TYPE_ON_APP_ERROR: {
                  int subType = (int) data[0];
  
                  if (subType == ConstantApp.AppError.NO_NETWORK_CONNECTION) {
                      showLongToast(getString(R.string.msg_no_network_connection));
                  }
  
                  break;
              }
  
              case AGEventHandler.EVENT_TYPE_ON_AGORA_MEDIA_ERROR: {
                  int error = (int) data[0];
                  String description = (String) data[1];
  
                  notifyMessageChanged(error + " " + description);
  
                  break;
              }
  
              case AGEventHandler.EVENT_TYPE_ON_AUDIO_ROUTE_CHANGED: {
                  notifyHeadsetPlugged((int) data[0]);
  
                  break;
              }
          }
      }
  
      public void notifyHeadsetPlugged(final int routing) {
          log.info("notifyHeadsetPlugged " + routing);
  
          mAudioRouting = routing;
  
          ImageView iv = (ImageView) findViewById(R.id.switch_speaker_id);
          if (mAudioRouting == 3) { // Speakerphone
              iv.setColorFilter(getResources().getColor(R.color.agora_blue), PorterDuff.Mode.MULTIPLY);
          } else {
              iv.clearColorFilter();
          }
      }
  }
  