Compare commits
4 Commits
developmen
...
text-form-
| Author | SHA1 | Date |
|---|---|---|
|
|
1c0f8a3c65 | 5 years ago |
|
|
fbc5b3ce54 | 5 years ago |
|
|
1d31a1a683 | 5 years ago |
|
|
52c5e128e1 | 5 years ago |
@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.hmg.hmgDr">
|
||||
<!-- Flutter needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@ -1,4 +1,4 @@
|
||||
package com.hmg.hmgDr.model;
|
||||
package com.example.doctor_app_flutter.Model;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
@ -1,4 +1,4 @@
|
||||
package com.hmg.hmgDr.Service;
|
||||
package com.example.doctor_app_flutter.Service;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
@ -0,0 +1,15 @@
|
||||
package com.example.doctor_app_flutter.Service;
|
||||
|
||||
import com.example.doctor_app_flutter.Model.GetSessionStatusModel;
|
||||
import com.example.doctor_app_flutter.Model.SessionStatusModel;
|
||||
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.POST;
|
||||
|
||||
public interface SessionStatusAPI {
|
||||
|
||||
@POST("LiveCareApi/DoctorApp/GetSessionStatus")
|
||||
Call<SessionStatusModel> getSessionStatusModelData(@Body GetSessionStatusModel getSessionStatusModel);
|
||||
}
|
||||
@ -0,0 +1,430 @@
|
||||
package com.example.doctor_app_flutter.ui;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.opengl.GLSurfaceView;
|
||||
import android.os.Bundle;
|
||||
import android.os.CountDownTimer;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.example.doctor_app_flutter.Model.GetSessionStatusModel;
|
||||
import com.example.doctor_app_flutter.Model.SessionStatusModel;
|
||||
import com.hmg.hmgDr.R;
|
||||
import com.opentok.android.Session;
|
||||
import com.opentok.android.Stream;
|
||||
import com.opentok.android.Publisher;
|
||||
import com.opentok.android.PublisherKit;
|
||||
import com.opentok.android.Subscriber;
|
||||
import com.opentok.android.BaseVideoRenderer;
|
||||
import com.opentok.android.OpentokError;
|
||||
import com.opentok.android.SubscriberKit;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import pub.devrel.easypermissions.AfterPermissionGranted;
|
||||
import pub.devrel.easypermissions.AppSettingsDialog;
|
||||
import pub.devrel.easypermissions.EasyPermissions;
|
||||
|
||||
public class VideoCallActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks,
|
||||
Session.SessionListener,
|
||||
Publisher.PublisherListener,
|
||||
Subscriber.VideoListener, VideoCallContract.VideoCallView {
|
||||
|
||||
private static final String TAG = VideoCallActivity.class.getSimpleName();
|
||||
|
||||
VideoCallContract.VideoCallPresenter videoCallPresenter;
|
||||
|
||||
private static final int RC_SETTINGS_SCREEN_PERM = 123;
|
||||
private static final int RC_VIDEO_APP_PERM = 124;
|
||||
|
||||
|
||||
private Session mSession;
|
||||
private Publisher mPublisher;
|
||||
private Subscriber mSubscriber;
|
||||
|
||||
private Handler mVolHandler, mConnectedHandler;
|
||||
private Runnable mVolRunnable, mConnectedRunnable;
|
||||
|
||||
private FrameLayout mPublisherViewContainer;
|
||||
private RelativeLayout mSubscriberViewContainer;
|
||||
private RelativeLayout controlPanel;
|
||||
|
||||
private String apiKey;
|
||||
private String sessionId;
|
||||
private String token;
|
||||
private String appLang;
|
||||
private String baseUrl;
|
||||
|
||||
private boolean isSwitchCameraClicked;
|
||||
private boolean isCameraClicked;
|
||||
private boolean isSpeckerClicked;
|
||||
private boolean isMicClicked;
|
||||
|
||||
private ImageView mCallBtn;
|
||||
private ImageView mCameraBtn;
|
||||
private ImageView mSwitchCameraBtn;
|
||||
private ImageView mspeckerBtn;
|
||||
private ImageView mMicBtn;
|
||||
|
||||
private ProgressBar progressBar;
|
||||
private CountDownTimer countDownTimer;
|
||||
private TextView progressBarTextView;
|
||||
private RelativeLayout progressBarLayout;
|
||||
|
||||
private boolean isConnected = false;
|
||||
|
||||
private GetSessionStatusModel sessionStatusModel;
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
setTheme(R.style.AppTheme);
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_video_call);
|
||||
Objects.requireNonNull(getSupportActionBar()).hide();
|
||||
initUI();
|
||||
requestPermissions();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
if (mSession == null) {
|
||||
return;
|
||||
}
|
||||
mSession.onPause();
|
||||
|
||||
if (isFinishing()) {
|
||||
disconnectSession();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (mSession == null) {
|
||||
return;
|
||||
}
|
||||
mSession.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
disconnectSession();
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private void initUI() {
|
||||
mPublisherViewContainer = (FrameLayout) findViewById(R.id.local_video_view_container);
|
||||
mSubscriberViewContainer = (RelativeLayout) findViewById(R.id.remote_video_view_container);
|
||||
|
||||
apiKey = getIntent().getStringExtra("apiKey");
|
||||
sessionId = getIntent().getStringExtra("sessionId");
|
||||
token = getIntent().getStringExtra("token");
|
||||
appLang = getIntent().getStringExtra("appLang");
|
||||
baseUrl = getIntent().getStringExtra("baseUrl");
|
||||
sessionStatusModel = getIntent().getParcelableExtra("sessionStatusModel");
|
||||
|
||||
controlPanel = findViewById(R.id.control_panel);
|
||||
|
||||
videoCallPresenter = new VideoCallPresenterImpl(this, baseUrl);
|
||||
|
||||
|
||||
mCallBtn = findViewById(R.id.btn_call);
|
||||
mCameraBtn = findViewById(R.id.btn_camera);
|
||||
mSwitchCameraBtn = findViewById(R.id.btn_switch_camera);
|
||||
mspeckerBtn = findViewById(R.id.btn_specker);
|
||||
mMicBtn = findViewById(R.id.btn_mic);
|
||||
|
||||
// progressBarLayout=findViewById(R.id.progressBar);
|
||||
// progressBar=findViewById(R.id.progress_bar);
|
||||
// progressBarTextView=findViewById(R.id.progress_bar_text);
|
||||
// progressBar.setVisibility(View.GONE);
|
||||
|
||||
hiddenButtons();
|
||||
|
||||
checkClientConnected();
|
||||
|
||||
mSubscriberViewContainer.setOnTouchListener((v, event) -> {
|
||||
controlPanel.setVisibility(View.VISIBLE);
|
||||
mVolHandler.removeCallbacks(mVolRunnable);
|
||||
mVolHandler.postDelayed(mVolRunnable, 5 * 1000);
|
||||
return true;
|
||||
});
|
||||
|
||||
if (appLang.equals("ar")) {
|
||||
progressBarLayout.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void checkClientConnected() {
|
||||
mConnectedHandler = new Handler();
|
||||
mConnectedRunnable = () -> {
|
||||
if (!isConnected) {
|
||||
videoCallPresenter.callClintConnected(sessionStatusModel);
|
||||
}
|
||||
};
|
||||
mConnectedHandler.postDelayed(mConnectedRunnable, 30 * 1000);
|
||||
|
||||
}
|
||||
|
||||
private void hiddenButtons() {
|
||||
mVolHandler = new Handler();
|
||||
mVolRunnable = new Runnable() {
|
||||
public void run() {
|
||||
controlPanel.setVisibility(View.GONE);
|
||||
}
|
||||
};
|
||||
mVolHandler.postDelayed(mVolRunnable, 5 * 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
|
||||
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPermissionsGranted(int requestCode, List<String> perms) {
|
||||
Log.d(TAG, "onPermissionsGranted:" + requestCode + ":" + perms.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPermissionsDenied(int requestCode, List<String> perms) {
|
||||
Log.d(TAG, "onPermissionsDenied:" + requestCode + ":" + perms.size());
|
||||
|
||||
if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
|
||||
new AppSettingsDialog.Builder(this)
|
||||
.setTitle(getString(R.string.title_settings_dialog))
|
||||
.setRationale(getString(R.string.rationale_ask_again))
|
||||
.setPositiveButton(getString(R.string.setting))
|
||||
.setNegativeButton(getString(R.string.cancel))
|
||||
.setRequestCode(RC_SETTINGS_SCREEN_PERM)
|
||||
.build()
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
@AfterPermissionGranted(RC_VIDEO_APP_PERM)
|
||||
private void requestPermissions() {
|
||||
String[] perms = {Manifest.permission.INTERNET, Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO};
|
||||
if (EasyPermissions.hasPermissions(this, perms)) {
|
||||
mSession = new Session.Builder(VideoCallActivity.this, apiKey, sessionId).build();
|
||||
mSession.setSessionListener(this);
|
||||
mSession.connect(token);
|
||||
} else {
|
||||
EasyPermissions.requestPermissions(this, getString(R.string.remaining_ar), RC_VIDEO_APP_PERM, perms);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnected(Session session) {
|
||||
Log.i(TAG, "Session Connected");
|
||||
|
||||
mPublisher = new Publisher.Builder(this).build();
|
||||
mPublisher.setPublisherListener(this);
|
||||
|
||||
mPublisherViewContainer.addView(mPublisher.getView());
|
||||
|
||||
if (mPublisher.getView() instanceof GLSurfaceView) {
|
||||
((GLSurfaceView) mPublisher.getView()).setZOrderOnTop(true);
|
||||
}
|
||||
|
||||
mSession.publish(mPublisher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnected(Session session) {
|
||||
Log.d(TAG, "onDisconnected: disconnected from session " + session.getSessionId());
|
||||
|
||||
mSession = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Session session, OpentokError opentokError) {
|
||||
Log.d(TAG, "onError: Error (" + opentokError.getMessage() + ") in session " + session.getSessionId());
|
||||
|
||||
Toast.makeText(this, "Session error. See the logcat please.", Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamReceived(Session session, Stream stream) {
|
||||
Log.d(TAG, "onStreamReceived: New stream " + stream.getStreamId() + " in session " + session.getSessionId());
|
||||
if (mSubscriber != null) {
|
||||
isConnected = true;
|
||||
return;
|
||||
}
|
||||
isConnected = true;
|
||||
subscribeToStream(stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamDropped(Session session, Stream stream) {
|
||||
Log.d(TAG, "onStreamDropped: Stream " + stream.getStreamId() + " dropped from session " + session.getSessionId());
|
||||
|
||||
if (mSubscriber == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSubscriber.getStream().equals(stream)) {
|
||||
mSubscriberViewContainer.removeView(mSubscriber.getView());
|
||||
mSubscriber.destroy();
|
||||
mSubscriber = null;
|
||||
}
|
||||
disconnectSession();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamCreated(PublisherKit publisherKit, Stream stream) {
|
||||
Log.d(TAG, "onStreamCreated: Own stream " + stream.getStreamId() + " created");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStreamDestroyed(PublisherKit publisherKit, Stream stream) {
|
||||
Log.d(TAG, "onStreamDestroyed: Own stream " + stream.getStreamId() + " destroyed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(PublisherKit publisherKit, OpentokError opentokError) {
|
||||
Log.d(TAG, "onError: Error (" + opentokError.getMessage() + ") in publisher");
|
||||
|
||||
Toast.makeText(this, "Session error. See the logcat please.", Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoDataReceived(SubscriberKit subscriberKit) {
|
||||
mSubscriber.setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE, BaseVideoRenderer.STYLE_VIDEO_FILL);
|
||||
mSubscriberViewContainer.addView(mSubscriber.getView());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoDisabled(SubscriberKit subscriberKit, String s) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoEnabled(SubscriberKit subscriberKit, String s) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoDisableWarning(SubscriberKit subscriberKit) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoDisableWarningLifted(SubscriberKit subscriberKit) {
|
||||
|
||||
}
|
||||
|
||||
private void subscribeToStream(Stream stream) {
|
||||
mSubscriber = new Subscriber.Builder(VideoCallActivity.this, stream).build();
|
||||
mSubscriber.setVideoListener(this);
|
||||
mSession.subscribe(mSubscriber);
|
||||
}
|
||||
|
||||
private void disconnectSession() {
|
||||
if (mSession == null) {
|
||||
setResult(Activity.RESULT_CANCELED);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSubscriber != null) {
|
||||
mSubscriberViewContainer.removeView(mSubscriber.getView());
|
||||
mSession.unsubscribe(mSubscriber);
|
||||
mSubscriber.destroy();
|
||||
mSubscriber = null;
|
||||
}
|
||||
|
||||
if (mPublisher != null) {
|
||||
mPublisherViewContainer.removeView(mPublisher.getView());
|
||||
mSession.unpublish(mPublisher);
|
||||
mPublisher.destroy();
|
||||
mPublisher = null;
|
||||
}
|
||||
mSession.disconnect();
|
||||
if (countDownTimer != null) {
|
||||
countDownTimer.cancel();
|
||||
}
|
||||
finish();
|
||||
}
|
||||
|
||||
public void onSwitchCameraClicked(View view) {
|
||||
if (mPublisher != null) {
|
||||
isSwitchCameraClicked = !isSwitchCameraClicked;
|
||||
mPublisher.cycleCamera();
|
||||
int res = isSwitchCameraClicked ? R.drawable.flip_disapled : R.drawable.flip_enabled;
|
||||
mSwitchCameraBtn.setImageResource(res);
|
||||
}
|
||||
}
|
||||
|
||||
public void onCameraClicked(View view) {
|
||||
if (mPublisher != null) {
|
||||
isCameraClicked = !isCameraClicked;
|
||||
mPublisher.setPublishVideo(!isCameraClicked);
|
||||
int res = isCameraClicked ? R.drawable.video_disanabled : R.drawable.video_enabled;
|
||||
mCameraBtn.setImageResource(res);
|
||||
}
|
||||
}
|
||||
|
||||
public void onSpeckerClicked(View view) {
|
||||
if (mSubscriber != null) {
|
||||
isSpeckerClicked = !isSpeckerClicked;
|
||||
mSubscriber.setSubscribeToAudio(!isSpeckerClicked);
|
||||
int res = isSpeckerClicked ? R.drawable.audio_disabled : R.drawable.audio_enabled;
|
||||
mspeckerBtn.setImageResource(res);
|
||||
}
|
||||
}
|
||||
|
||||
public void onMicClicked(View view) {
|
||||
|
||||
if (mPublisher != null) {
|
||||
isMicClicked = !isMicClicked;
|
||||
mPublisher.setPublishAudio(!isMicClicked);
|
||||
int res = isMicClicked ? R.drawable.mic_disabled : R.drawable.mic_enabled;
|
||||
mMicBtn.setImageResource(res);
|
||||
}
|
||||
}
|
||||
|
||||
public void onCallClicked(View view) {
|
||||
disconnectSession();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCallSuccessful(SessionStatusModel sessionStatusModel) {
|
||||
if (sessionStatusModel.getSessionStatus() == 2 || sessionStatusModel.getSessionStatus() == 3) {
|
||||
Intent returnIntent = new Intent();
|
||||
returnIntent.putExtra("sessionStatusNotRespond", sessionStatusModel);
|
||||
setResult(Activity.RESULT_OK, returnIntent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package com.example.doctor_app_flutter.ui;
|
||||
|
||||
import com.example.doctor_app_flutter.Model.GetSessionStatusModel;
|
||||
import com.example.doctor_app_flutter.Model.SessionStatusModel;
|
||||
|
||||
public interface VideoCallContract {
|
||||
|
||||
interface VideoCallView{
|
||||
|
||||
void onCallSuccessful(SessionStatusModel sessionStatusModel);
|
||||
void onFailure();
|
||||
}
|
||||
|
||||
interface VideoCallPresenter {
|
||||
|
||||
void callClintConnected(GetSessionStatusModel statusModel);
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
package com.hmg.hmgDr
|
||||
|
||||
import com.hmg.hmgDr.globalErrorHandler.LoggingExceptionHandler
|
||||
import io.flutter.app.FlutterApplication
|
||||
|
||||
class AppApplication : FlutterApplication() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
// LoggingExceptionHandler(this, "ErrorFile")
|
||||
}
|
||||
}
|
||||
@ -1,284 +1,101 @@
|
||||
package com.hmg.hmgDr
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.NonNull
|
||||
import com.example.doctor_app_flutter.Model.GetSessionStatusModel
|
||||
import com.example.doctor_app_flutter.Model.SessionStatusModel
|
||||
import com.example.doctor_app_flutter.ui.VideoCallActivity
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.hmg.hmgDr.Service.VideoStreamFloatingWidgetService
|
||||
import com.hmg.hmgDr.model.GetSessionStatusModel
|
||||
import com.hmg.hmgDr.model.SessionStatusModel
|
||||
import com.hmg.hmgDr.ui.VideoCallResponseListener
|
||||
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugins.GeneratedPluginRegistrant
|
||||
import pub.devrel.easypermissions.AfterPermissionGranted
|
||||
import pub.devrel.easypermissions.AppSettingsDialog
|
||||
import pub.devrel.easypermissions.EasyPermissions
|
||||
|
||||
|
||||
class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler,
|
||||
VideoCallResponseListener {
|
||||
|
||||
/* Permission request code to draw over other apps */
|
||||
private val DRAW_OVER_OTHER_APP_PERMISSION_REQUEST_CODE = 1222
|
||||
class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler {
|
||||
|
||||
private val CHANNEL = "Dr.cloudSolution/videoCall"
|
||||
private lateinit var methodChannel: MethodChannel
|
||||
private var result: MethodChannel.Result? = null
|
||||
private var call: MethodCall? = null
|
||||
private val LAUNCH_VIDEO: Int = 1
|
||||
|
||||
private var serviceIntent: Intent? = null
|
||||
private var videoStreamService: VideoStreamFloatingWidgetService? = null
|
||||
private var bound = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||
GeneratedPluginRegistrant.registerWith(flutterEngine)
|
||||
|
||||
methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
|
||||
methodChannel.setMethodCallHandler(this)
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler(this)
|
||||
}
|
||||
|
||||
|
||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||
|
||||
this.result = result
|
||||
this.call = call
|
||||
|
||||
when (call.method) {
|
||||
"openVideoCall" -> {
|
||||
val apiKey = call.argument<String>("kApiKey")
|
||||
val sessionId = call.argument<String>("kSessionId")
|
||||
val token = call.argument<String>("kToken")
|
||||
val appLang = call.argument<String>("appLang")
|
||||
val baseUrl = call.argument<String>("baseUrl")
|
||||
if (call.method == "openVideoCall") {
|
||||
val apiKey = call.argument<String>("kApiKey")
|
||||
val sessionId = call.argument<String>("kSessionId")
|
||||
val token = call.argument<String>("kToken")
|
||||
val appLang = call.argument<String>("appLang")
|
||||
val baseUrl = call.argument<String>("baseUrl")
|
||||
|
||||
// Session Status model
|
||||
val VC_ID = call.argument<Int>("VC_ID")
|
||||
val tokenID = call.argument<String>("TokenID")
|
||||
val generalId = call.argument<String>("generalId")
|
||||
val doctorId = call.argument<Int>("DoctorId")
|
||||
val patientName = call.argument<String>("patientName")
|
||||
val isRecording = call.argument<Boolean>("isRecording")
|
||||
// Session Status model
|
||||
val VC_ID = call.argument<Int>("VC_ID")
|
||||
val tokenID = call.argument<String>("TokenID")
|
||||
val generalId = call.argument<String>("generalId")
|
||||
val doctorId = call.argument<Int>("DoctorId")
|
||||
|
||||
val sessionStatusModel =
|
||||
GetSessionStatusModel(
|
||||
VC_ID,
|
||||
tokenID,
|
||||
generalId,
|
||||
doctorId,
|
||||
patientName,
|
||||
isRecording!!
|
||||
)
|
||||
val sessionStatusModel = GetSessionStatusModel(VC_ID, tokenID, generalId, doctorId)
|
||||
|
||||
|
||||
openVideoCall(apiKey, sessionId, token, appLang, baseUrl, sessionStatusModel)
|
||||
openVideoCall(apiKey, sessionId, token, appLang, baseUrl, sessionStatusModel)
|
||||
|
||||
}
|
||||
"closeVideoCall" -> {
|
||||
videoStreamService?.closeVideoCall()
|
||||
}
|
||||
else -> {
|
||||
result.notImplemented()
|
||||
}
|
||||
} else {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
private fun openVideoCall(
|
||||
apiKey: String?,
|
||||
sessionId: String?,
|
||||
token: String?,
|
||||
appLang: String?,
|
||||
baseUrl: String?,
|
||||
sessionStatusModel: GetSessionStatusModel
|
||||
) {
|
||||
|
||||
val arguments = Bundle()
|
||||
arguments.putString("apiKey", apiKey)
|
||||
arguments.putString("sessionId", sessionId)
|
||||
arguments.putString("token", token)
|
||||
arguments.putString("appLang", appLang)
|
||||
arguments.putString("baseUrl", baseUrl)
|
||||
arguments.putParcelable("sessionStatusModel", sessionStatusModel)
|
||||
|
||||
// start service
|
||||
// serviceIntent = Intent(this@MainActivity, VideoStreamContainerService::class.java)
|
||||
if (videoStreamService == null || videoStreamService?.serviceRunning == false) {
|
||||
serviceIntent = Intent(this@MainActivity, VideoStreamFloatingWidgetService::class.java)
|
||||
serviceIntent?.run {
|
||||
putExtras(arguments)
|
||||
action = VideoStreamFloatingWidgetService.ACTION_START_CALL
|
||||
}
|
||||
checkFloatingWidgetPermission()
|
||||
}
|
||||
}
|
||||
private fun openVideoCall(apiKey: String?, sessionId: String?, token: String?, appLang: String?, baseUrl: String?, sessionStatusModel: GetSessionStatusModel) {
|
||||
// val videoCallActivity = VideoCallActivity()
|
||||
|
||||
private fun checkFloatingWidgetPermission() {
|
||||
// Check if the application has draw over other apps permission or not?
|
||||
// This permission is by default available for API<23. But for API > 23
|
||||
// you have to ask for the permission in runtime.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
|
||||
//If the draw over permission is not available open the settings screen
|
||||
//to grant the permission.
|
||||
val intent = Intent(
|
||||
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
||||
Uri.parse("package:$packageName")
|
||||
)
|
||||
startActivityForResult(intent, DRAW_OVER_OTHER_APP_PERMISSION_REQUEST_CODE)
|
||||
} else { //If permission is granted start floating widget service
|
||||
startFloatingWidgetService()
|
||||
}
|
||||
}
|
||||
val intent = Intent(this, VideoCallActivity::class.java)
|
||||
intent.putExtra("apiKey", apiKey)
|
||||
intent.putExtra("sessionId", sessionId)
|
||||
intent.putExtra("token", token)
|
||||
intent.putExtra("appLang", appLang)
|
||||
intent.putExtra("baseUrl", baseUrl)
|
||||
intent.putExtra("sessionStatusModel", sessionStatusModel)
|
||||
startActivityForResult(intent, LAUNCH_VIDEO)
|
||||
|
||||
private fun startFloatingWidgetService() {
|
||||
startService(serviceIntent)
|
||||
bindService()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (bound) {
|
||||
unbindService()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (requestCode == DRAW_OVER_OTHER_APP_PERMISSION_REQUEST_CODE) {
|
||||
//Check if the permission is granted or not.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (Settings.canDrawOverlays(this)) {
|
||||
startFloatingWidgetService()
|
||||
} else {
|
||||
//Permission is not available then display toast
|
||||
Toast.makeText(
|
||||
this,
|
||||
"Draw over other app permission not available. App won\\'t work without permission. Please try again.",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
} else {
|
||||
startFloatingWidgetService()
|
||||
}
|
||||
} else {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCallFinished(resultCode: Int, intent: Intent?) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
val result: SessionStatusModel? = intent?.getParcelableExtra("sessionStatusNotRespond")
|
||||
val callResponse: HashMap<String, String> = HashMap()
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
var asd = "";
|
||||
if (requestCode == LAUNCH_VIDEO) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
val result : SessionStatusModel? = data?.getParcelableExtra("sessionStatusNotRespond")
|
||||
val callResponse : HashMap<String, String> = HashMap()
|
||||
|
||||
val sessionStatus: HashMap<String, String> = HashMap()
|
||||
val gson = GsonBuilder().serializeNulls().create()
|
||||
val sessionStatus : HashMap<String, String> = HashMap()
|
||||
val gson = GsonBuilder().serializeNulls().create()
|
||||
|
||||
callResponse["callResponse"] = "CallNotRespond"
|
||||
val jsonRes = gson.toJson(result)
|
||||
callResponse["sessionStatus"] = jsonRes
|
||||
callResponse["callResponse"] = "CallNotRespond"
|
||||
val jsonRes = gson.toJson(result)
|
||||
callResponse["sessionStatus"] = jsonRes
|
||||
|
||||
try {
|
||||
this.result?.success(callResponse)
|
||||
} catch (e: Exception) {
|
||||
Log.e("onVideoCallFinished", "${e.message}.")
|
||||
}
|
||||
} else if (resultCode == Activity.RESULT_CANCELED) {
|
||||
val callResponse: HashMap<String, String> = HashMap()
|
||||
callResponse["callResponse"] = "CallEnd"
|
||||
try {
|
||||
result?.success(callResponse)
|
||||
} catch (e: Exception) {
|
||||
Log.e("onVideoCallFinished", "${e.message}.")
|
||||
}
|
||||
}
|
||||
|
||||
stopService(serviceIntent)
|
||||
}
|
||||
|
||||
override fun minimizeVideoEvent(isMinimize: Boolean) {
|
||||
if (isMinimize)
|
||||
methodChannel.invokeMethod("onCallConnected", null)
|
||||
else {
|
||||
methodChannel.invokeMethod("onCallDisconnected", null)
|
||||
unbindService()
|
||||
videoStreamService?.serviceRunning = false
|
||||
videoStreamService = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (videoStreamService != null && videoStreamService?.serviceRunning == true && videoStreamService?.isFullScreen!!) {
|
||||
videoStreamService!!.onMinimizedClicked()
|
||||
} else {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
if (resultCode == Activity.RESULT_CANCELED) {
|
||||
val callResponse : HashMap<String, String> = HashMap()
|
||||
callResponse["callResponse"] = "CallEnd"
|
||||
|
||||
override fun onPause() {
|
||||
if (videoStreamService != null && videoStreamService?.serviceRunning == true && videoStreamService?.isFullScreen!!) {
|
||||
videoStreamService!!.onMinimizedClicked()
|
||||
}
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
// override fun onStart() {
|
||||
// super.onStart()
|
||||
// bindService()
|
||||
// }
|
||||
//
|
||||
// override fun onStop() {
|
||||
// super.onStop()
|
||||
// unbindService()
|
||||
// }
|
||||
|
||||
private fun bindService() {
|
||||
serviceIntent?.run {
|
||||
if (videoStreamService != null && !videoStreamService!!.serviceRunning) {
|
||||
startService(this)
|
||||
result?.success(callResponse)
|
||||
}
|
||||
bindService(this, serviceConnection, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun unbindService() {
|
||||
if (bound) {
|
||||
videoStreamService?.videoCallResponseListener = null // unregister
|
||||
unbindService(serviceConnection)
|
||||
bound = false
|
||||
}
|
||||
}
|
||||
|
||||
private val serviceConnection: ServiceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
// val binder: VideoStreamContainerService.VideoStreamBinder =
|
||||
// service as VideoStreamContainerService.VideoStreamBinder
|
||||
val binder: VideoStreamFloatingWidgetService.VideoStreamBinder =
|
||||
service as VideoStreamFloatingWidgetService.VideoStreamBinder
|
||||
videoStreamService = binder.service
|
||||
bound = true
|
||||
videoStreamService!!.videoCallResponseListener = this@MainActivity // register
|
||||
videoStreamService?.serviceRunning = true
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
bound = false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,234 +0,0 @@
|
||||
package com.hmg.hmgDr.Service
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.Point
|
||||
import android.os.Build
|
||||
import android.os.CountDownTimer
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import androidx.core.view.GestureDetectorCompat
|
||||
import com.hmg.hmgDr.R
|
||||
import com.hmg.hmgDr.util.ViewsUtil
|
||||
|
||||
abstract class BaseMovingFloatingWidget : Service() {
|
||||
|
||||
val szWindow = Point()
|
||||
lateinit var windowManagerParams: WindowManager.LayoutParams
|
||||
var mWindowManager: WindowManager? = null
|
||||
var floatingWidgetView: View? = null
|
||||
lateinit var floatingViewContainer: View
|
||||
|
||||
lateinit var mDetector: GestureDetectorCompat
|
||||
|
||||
private var xInitCord = 0
|
||||
private var yInitCord: Int = 0
|
||||
private var xInitMargin: Int = 0
|
||||
private var yInitMargin: Int = 0
|
||||
|
||||
/* Add Floating Widget View to Window Manager */
|
||||
open fun addFloatingWidgetView() {
|
||||
mWindowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
||||
|
||||
//Init LayoutInflater
|
||||
val inflater = getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||
//Inflate the removing view layout we created
|
||||
floatingWidgetView = inflater.inflate(R.layout.activity_video_call, null)
|
||||
|
||||
//Add the view to the window.
|
||||
windowManagerParams =
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
WindowManager.LayoutParams(
|
||||
WindowManager.LayoutParams.WRAP_CONTENT,
|
||||
WindowManager.LayoutParams.WRAP_CONTENT,
|
||||
WindowManager.LayoutParams.TYPE_PHONE,
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
|
||||
PixelFormat.TRANSLUCENT
|
||||
)
|
||||
} else {
|
||||
WindowManager.LayoutParams(
|
||||
WindowManager.LayoutParams.WRAP_CONTENT,
|
||||
WindowManager.LayoutParams.WRAP_CONTENT,
|
||||
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
|
||||
PixelFormat.TRANSLUCENT
|
||||
)
|
||||
}
|
||||
|
||||
//Specify the view position
|
||||
windowManagerParams.gravity = Gravity.TOP or Gravity.START
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
val dragListener: View.OnTouchListener = View.OnTouchListener { _, event ->
|
||||
mDetector.onTouchEvent(event)
|
||||
|
||||
//Get Floating widget view params
|
||||
val layoutParams: WindowManager.LayoutParams =
|
||||
floatingWidgetView!!.layoutParams as WindowManager.LayoutParams
|
||||
//get the touch location coordinates
|
||||
val x_cord = event.rawX.toInt()
|
||||
val y_cord = event.rawY.toInt()
|
||||
val x_cord_Destination: Int
|
||||
var y_cord_Destination: Int
|
||||
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
xInitCord = x_cord
|
||||
yInitCord = y_cord
|
||||
|
||||
//remember the initial position.
|
||||
xInitMargin = layoutParams.x
|
||||
yInitMargin = layoutParams.y
|
||||
}
|
||||
MotionEvent.ACTION_UP -> {
|
||||
//Get the difference between initial coordinate and current coordinate
|
||||
val x_diff: Int = x_cord - xInitCord
|
||||
val y_diff: Int = y_cord - yInitCord
|
||||
|
||||
y_cord_Destination = yInitMargin + y_diff
|
||||
val barHeight: Int = ViewsUtil.getStatusBarHeight(applicationContext)
|
||||
if (y_cord_Destination < 0) {
|
||||
y_cord_Destination = 0
|
||||
// y_cord_Destination =
|
||||
// -(szWindow.y - (videoCallContainer.height /*+ barHeight*/))
|
||||
// y_cord_Destination = -(szWindow.y / 2)
|
||||
} else if (y_cord_Destination + (floatingViewContainer.height + barHeight) > szWindow.y) {
|
||||
y_cord_Destination = szWindow.y - (floatingViewContainer.height + barHeight)
|
||||
// y_cord_Destination = (szWindow.y / 2)
|
||||
}
|
||||
layoutParams.y = y_cord_Destination
|
||||
|
||||
//reset position if user drags the floating view
|
||||
resetPosition(x_cord)
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
val x_diff_move: Int = x_cord - xInitCord
|
||||
val y_diff_move: Int = y_cord - yInitCord
|
||||
x_cord_Destination = xInitMargin + x_diff_move
|
||||
y_cord_Destination = yInitMargin + y_diff_move
|
||||
|
||||
layoutParams.x = x_cord_Destination
|
||||
layoutParams.y = y_cord_Destination
|
||||
|
||||
//Update the layout with new X & Y coordinate
|
||||
mWindowManager?.updateViewLayout(floatingWidgetView, layoutParams)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/**
|
||||
* OnTouch actions
|
||||
*/
|
||||
class MyGestureListener(
|
||||
val onTabCall: () -> Unit,
|
||||
val miniCircleDoubleTap: () -> Unit
|
||||
) : GestureDetector.SimpleOnGestureListener() {
|
||||
|
||||
override fun onSingleTapConfirmed(event: MotionEvent): Boolean {
|
||||
// onTabCall()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDoubleTap(e: MotionEvent?): Boolean {
|
||||
miniCircleDoubleTap()
|
||||
return super.onDoubleTap(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Reset position of Floating Widget view on dragging */
|
||||
fun resetPosition(x_cord_now: Int) {
|
||||
if (x_cord_now <= szWindow.x / 2) {
|
||||
moveToLeft(x_cord_now)
|
||||
} else {
|
||||
moveToRight(x_cord_now)
|
||||
}
|
||||
}
|
||||
|
||||
/* Method to move the Floating widget view to Left */
|
||||
private fun moveToLeft(current_x_cord: Int) {
|
||||
|
||||
val mParams: WindowManager.LayoutParams =
|
||||
floatingWidgetView!!.layoutParams as WindowManager.LayoutParams
|
||||
|
||||
mParams.x =
|
||||
(szWindow.x - current_x_cord * current_x_cord - floatingViewContainer.width).toInt()
|
||||
|
||||
try {
|
||||
mWindowManager?.updateViewLayout(floatingWidgetView, mParams)
|
||||
} catch (e: Exception) {
|
||||
Log.e("windowManagerUpdate", "${e.localizedMessage}.")
|
||||
}
|
||||
val x = szWindow.x - current_x_cord
|
||||
object : CountDownTimer(500, 5) {
|
||||
//get params of Floating Widget view
|
||||
val mParams: WindowManager.LayoutParams =
|
||||
floatingWidgetView!!.layoutParams as WindowManager.LayoutParams
|
||||
|
||||
override fun onTick(t: Long) {
|
||||
val step = (500 - t) / 5
|
||||
// mParams.x = 0 - (current_x_cord * current_x_cord * step).toInt()
|
||||
mParams.x =
|
||||
(szWindow.x - current_x_cord * current_x_cord * step - floatingViewContainer.width).toInt()
|
||||
|
||||
try {
|
||||
mWindowManager?.updateViewLayout(floatingWidgetView, mParams)
|
||||
} catch (e: Exception) {
|
||||
Log.e("windowManagerUpdate", "${e.localizedMessage}.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
mParams.x = -(szWindow.x - floatingViewContainer.width)
|
||||
|
||||
try {
|
||||
mWindowManager?.updateViewLayout(floatingWidgetView, mParams)
|
||||
} catch (e: Exception) {
|
||||
Log.e("windowManagerUpdate", "${e.localizedMessage}.")
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
/* Method to move the Floating widget view to Right */
|
||||
private fun moveToRight(current_x_cord: Int) {
|
||||
object : CountDownTimer(500, 5) {
|
||||
//get params of Floating Widget view
|
||||
val mParams: WindowManager.LayoutParams =
|
||||
floatingWidgetView!!.layoutParams as WindowManager.LayoutParams
|
||||
|
||||
override fun onTick(t: Long) {
|
||||
val step = (500 - t) / 5
|
||||
mParams.x =
|
||||
(szWindow.x + current_x_cord * current_x_cord * step - floatingViewContainer.width).toInt()
|
||||
|
||||
try {
|
||||
mWindowManager?.updateViewLayout(floatingWidgetView, mParams)
|
||||
} catch (e: Exception) {
|
||||
Log.e("windowManagerUpdate", "${e.localizedMessage}.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
mParams.x = szWindow.x - floatingViewContainer.width
|
||||
|
||||
mWindowManager?.updateViewLayout(floatingWidgetView, mParams)
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
/***
|
||||
* Utils
|
||||
*/
|
||||
|
||||
fun getWindowManagerDefaultDisplay() {
|
||||
val w = mWindowManager!!.defaultDisplay.width
|
||||
val h = mWindowManager!!.defaultDisplay.height
|
||||
szWindow[w] = h
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
package com.hmg.hmgDr.Service;
|
||||
|
||||
import com.hmg.hmgDr.model.ChangeCallStatusRequestModel;
|
||||
import com.hmg.hmgDr.model.GetSessionStatusModel;
|
||||
import com.hmg.hmgDr.model.SessionStatusModel;
|
||||
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.POST;
|
||||
|
||||
public interface SessionStatusAPI {
|
||||
|
||||
@POST("LiveCareApi/DoctorApp/GetSessionStatus")
|
||||
Call<SessionStatusModel> getSessionStatusModelData(@Body GetSessionStatusModel getSessionStatusModel);
|
||||
|
||||
@POST("LiveCareApi/DoctorApp/ChangeCallStatus")
|
||||
Call<SessionStatusModel> changeCallStatus(@Body ChangeCallStatusRequestModel changeCallStatusRequestModel);
|
||||
}
|
||||
@ -1,91 +0,0 @@
|
||||
package com.hmg.hmgDr.Service
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.Binder
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import com.hmg.hmgDr.MainActivity
|
||||
import com.hmg.hmgDr.ui.VideoCallResponseListener
|
||||
import com.hmg.hmgDr.ui.fragment.VideoCallFragment
|
||||
|
||||
class VideoStreamContainerService : Service(), VideoCallResponseListener {
|
||||
|
||||
var videoCallResponseListener: VideoCallResponseListener? = null
|
||||
var mActivity: MainActivity? = null
|
||||
set(value) {
|
||||
field = value
|
||||
if (field != null) {
|
||||
setDialogFragment()
|
||||
}
|
||||
}
|
||||
var arguments: Bundle? = null
|
||||
var serviceRunning: Boolean = false
|
||||
|
||||
|
||||
private val serviceBinder: IBinder = VideoStreamBinder()
|
||||
private var dialogFragment: VideoCallFragment? = null
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder {
|
||||
return serviceBinder
|
||||
}
|
||||
|
||||
private fun setDialogFragment() {
|
||||
mActivity?.run {
|
||||
if (dialogFragment == null) {
|
||||
val transaction = supportFragmentManager.beginTransaction()
|
||||
dialogFragment = VideoCallFragment.newInstance(arguments!!)
|
||||
dialogFragment?.let {
|
||||
it.setCallListener(this@VideoStreamContainerService)
|
||||
it.isCancelable = true
|
||||
if (it.isAdded) {
|
||||
it.dismiss()
|
||||
} else {
|
||||
it.show(transaction, "dialog")
|
||||
}
|
||||
}
|
||||
} else if (!dialogFragment!!.isVisible) {
|
||||
val transaction = supportFragmentManager.beginTransaction()
|
||||
dialogFragment!!.show(transaction, "dialog")
|
||||
} else {
|
||||
// don't do anything
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun closeVideoCall(){
|
||||
dialogFragment?.onCallClicked()
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (intent != null && intent.extras != null) {
|
||||
arguments = intent.extras
|
||||
}
|
||||
// Toast.makeText(this, "Service started by user.", Toast.LENGTH_LONG).show()
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
// Toast.makeText(this, "Service destroyed by user.", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
inner class VideoStreamBinder : Binder() {
|
||||
val service: VideoStreamContainerService
|
||||
get() = this@VideoStreamContainerService
|
||||
}
|
||||
|
||||
override fun onCallFinished(resultCode: Int, intent: Intent?) {
|
||||
dialogFragment = null
|
||||
videoCallResponseListener?.onCallFinished(resultCode, intent)
|
||||
}
|
||||
|
||||
override fun errorHandle(message: String) {
|
||||
dialogFragment = null
|
||||
// Toast.makeText(this, message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
override fun minimizeVideoEvent(isMinimize: Boolean) {
|
||||
videoCallResponseListener?.minimizeVideoEvent(isMinimize)
|
||||
}
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
package com.hmg.hmgDr.globalErrorHandler
|
||||
|
||||
import android.os.Environment
|
||||
import java.io.BufferedWriter
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
import java.io.IOException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
|
||||
object FileUtil {
|
||||
|
||||
val sdf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS")
|
||||
|
||||
fun pushLog(body: String?) {
|
||||
try {
|
||||
val date = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date())
|
||||
val time = SimpleDateFormat("HH:MM:SS", Locale.getDefault()).format(Date())
|
||||
val root =
|
||||
File(Environment.getExternalStorageDirectory(),"error_log_dir")
|
||||
// if external memory exists and folder with name Notes
|
||||
if (!root.exists()) {
|
||||
root.mkdirs() // this will create folder.
|
||||
}
|
||||
val oldFile = File(root, "error" + sdf.format(Date()).toString() + ".txt") // old file
|
||||
if (oldFile.exists()) oldFile.delete()
|
||||
val filepath = File(root, "error$date.txt") // file path to save
|
||||
val bufferedWriter = BufferedWriter(FileWriter(filepath, true))
|
||||
bufferedWriter.append("\r\n")
|
||||
bufferedWriter.append("\r\n").append(body).append(" Time : ").append(time)
|
||||
bufferedWriter.flush()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalStateException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
package com.hmg.hmgDr.globalErrorHandler
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.hmg.hmgDr.MainActivity
|
||||
import com.hmg.hmgDr.globalErrorHandler.FileUtil.pushLog
|
||||
|
||||
|
||||
class LoggingExceptionHandler(private val context: Context, ErrorFile: String) :
|
||||
Thread.UncaughtExceptionHandler {
|
||||
private val rootHandler: Thread.UncaughtExceptionHandler
|
||||
override fun uncaughtException(t: Thread, e: Throwable) {
|
||||
object : Thread() {
|
||||
override fun run() {
|
||||
pushLog("UnCaught Exception is thrown in $error$e")
|
||||
try {
|
||||
sleep(500)
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
context.startActivity(intent)
|
||||
} catch (e1: Exception) {
|
||||
e1.printStackTrace()
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
rootHandler.uncaughtException(t, e)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = LoggingExceptionHandler::class.java.simpleName
|
||||
lateinit var error: String
|
||||
}
|
||||
|
||||
init {
|
||||
error = "$ErrorFile.error "
|
||||
rootHandler = Thread.getDefaultUncaughtExceptionHandler()
|
||||
Thread.setDefaultUncaughtExceptionHandler(this)
|
||||
}
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
package com.hmg.hmgDr.globalErrorHandler
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
class UCEDefaultActivity : AppCompatActivity() {
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
package com.hmg.hmgDr.globalErrorHandler
|
||||
|
||||
import androidx.core.content.FileProvider
|
||||
|
||||
class UCEFileProvider : FileProvider() {
|
||||
}
|
||||
@ -1,280 +0,0 @@
|
||||
package com.hmg.hmgDr.globalErrorHandler
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Date;
|
||||
import java.util.Deque;
|
||||
import java.util.Locale;
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class UCEHandler(val builder: Builder) {
|
||||
|
||||
val EXTRA_STACK_TRACE = "EXTRA_STACK_TRACE"
|
||||
val EXTRA_ACTIVITY_LOG = "EXTRA_ACTIVITY_LOG"
|
||||
private val TAG = "UCEHandler"
|
||||
private val UCE_HANDLER_PACKAGE_NAME = "com.rohitss.uceh"
|
||||
private val DEFAULT_HANDLER_PACKAGE_NAME = "com.android.internal.os"
|
||||
private val MAX_STACK_TRACE_SIZE = 131071 //128 KB - 1
|
||||
|
||||
private val MAX_ACTIVITIES_IN_LOG = 50
|
||||
private val SHARED_PREFERENCES_FILE = "uceh_preferences"
|
||||
private val SHARED_PREFERENCES_FIELD_TIMESTAMP = "last_crash_timestamp"
|
||||
private val activityLog: Deque<String> = ArrayDeque(MAX_ACTIVITIES_IN_LOG)
|
||||
var COMMA_SEPARATED_EMAIL_ADDRESSES: String? = null
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private var application: Application? = null
|
||||
private var isInBackground = true
|
||||
private var isBackgroundMode = false
|
||||
private var isUCEHEnabled = false
|
||||
private var isTrackActivitiesEnabled = false
|
||||
private var lastActivityCreated: WeakReference<Activity?> = WeakReference(null)
|
||||
|
||||
fun UCEHandler(builder: Builder) {
|
||||
isUCEHEnabled = builder.isUCEHEnabled
|
||||
isTrackActivitiesEnabled = builder.isTrackActivitiesEnabled
|
||||
isBackgroundMode = builder.isBackgroundModeEnabled
|
||||
COMMA_SEPARATED_EMAIL_ADDRESSES = builder.commaSeparatedEmailAddresses
|
||||
setUCEHandler(builder.context)
|
||||
}
|
||||
|
||||
private fun setUCEHandler(context: Context?) {
|
||||
try {
|
||||
if (context != null) {
|
||||
val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
|
||||
if (oldHandler != null && oldHandler.javaClass.name.startsWith(
|
||||
UCE_HANDLER_PACKAGE_NAME
|
||||
)
|
||||
) {
|
||||
Log.e(TAG, "UCEHandler was already installed, doing nothing!")
|
||||
} else {
|
||||
if (oldHandler != null && !oldHandler.javaClass.name.startsWith(
|
||||
DEFAULT_HANDLER_PACKAGE_NAME
|
||||
)
|
||||
) {
|
||||
Log.e(
|
||||
TAG,
|
||||
"You already have an UncaughtExceptionHandler. If you use a custom UncaughtExceptionHandler, it should be initialized after UCEHandler! Installing anyway, but your original handler will not be called."
|
||||
)
|
||||
}
|
||||
application = context.getApplicationContext() as Application
|
||||
//Setup UCE Handler.
|
||||
Thread.setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler { thread, throwable ->
|
||||
if (isUCEHEnabled) {
|
||||
Log.e(
|
||||
TAG,
|
||||
"App crashed, executing UCEHandler's UncaughtExceptionHandler",
|
||||
throwable
|
||||
)
|
||||
if (hasCrashedInTheLastSeconds(application!!)) {
|
||||
Log.e(
|
||||
TAG,
|
||||
"App already crashed recently, not starting custom error activity because we could enter a restart loop. Are you sure that your app does not crash directly on init?",
|
||||
throwable
|
||||
)
|
||||
if (oldHandler != null) {
|
||||
oldHandler.uncaughtException(thread, throwable)
|
||||
return@UncaughtExceptionHandler
|
||||
}
|
||||
} else {
|
||||
setLastCrashTimestamp(application!!, Date().getTime())
|
||||
if (!isInBackground || isBackgroundMode) {
|
||||
val intent = Intent(application, UCEDefaultActivity::class.java)
|
||||
val sw = StringWriter()
|
||||
val pw = PrintWriter(sw)
|
||||
throwable.printStackTrace(pw)
|
||||
var stackTraceString: String = sw.toString()
|
||||
if (stackTraceString.length > MAX_STACK_TRACE_SIZE) {
|
||||
val disclaimer = " [stack trace too large]"
|
||||
stackTraceString = stackTraceString.substring(
|
||||
0,
|
||||
MAX_STACK_TRACE_SIZE - disclaimer.length
|
||||
) + disclaimer
|
||||
}
|
||||
intent.putExtra(EXTRA_STACK_TRACE, stackTraceString)
|
||||
if (isTrackActivitiesEnabled) {
|
||||
val activityLogStringBuilder = StringBuilder()
|
||||
while (!activityLog.isEmpty()) {
|
||||
activityLogStringBuilder.append(activityLog.poll())
|
||||
}
|
||||
intent.putExtra(
|
||||
EXTRA_ACTIVITY_LOG,
|
||||
activityLogStringBuilder.toString()
|
||||
)
|
||||
}
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
application!!.startActivity(intent)
|
||||
} else {
|
||||
if (oldHandler != null) {
|
||||
oldHandler.uncaughtException(thread, throwable)
|
||||
return@UncaughtExceptionHandler
|
||||
}
|
||||
//If it is null (should not be), we let it continue and kill the process or it will be stuck
|
||||
}
|
||||
}
|
||||
val lastActivity: Activity? = lastActivityCreated.get()
|
||||
if (lastActivity != null) {
|
||||
lastActivity.finish()
|
||||
lastActivityCreated.clear()
|
||||
}
|
||||
killCurrentProcess()
|
||||
} else oldHandler?.uncaughtException(thread, throwable)
|
||||
})
|
||||
application!!.registerActivityLifecycleCallbacks(object :
|
||||
Application.ActivityLifecycleCallbacks {
|
||||
val dateFormat: DateFormat =
|
||||
SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
|
||||
var currentlyStartedActivities = 0
|
||||
|
||||
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
|
||||
if (activity.javaClass !== UCEDefaultActivity::class.java) {
|
||||
lastActivityCreated = WeakReference(activity)
|
||||
}
|
||||
if (isTrackActivitiesEnabled) {
|
||||
activityLog.add(
|
||||
dateFormat.format(Date())
|
||||
.toString() + ": " + activity.javaClass
|
||||
.getSimpleName() + " created\n"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityStarted(activity: Activity) {
|
||||
currentlyStartedActivities++
|
||||
isInBackground = currentlyStartedActivities == 0
|
||||
}
|
||||
|
||||
override fun onActivityResumed(activity: Activity) {
|
||||
if (isTrackActivitiesEnabled) {
|
||||
activityLog.add(
|
||||
dateFormat.format(Date())
|
||||
.toString() + ": " + activity.javaClass
|
||||
.simpleName + " resumed\n"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityPaused(activity: Activity) {
|
||||
if (isTrackActivitiesEnabled) {
|
||||
activityLog.add(
|
||||
dateFormat.format(Date())
|
||||
.toString() + ": " + activity.javaClass
|
||||
.simpleName + " paused\n"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityStopped(activity: Activity) {
|
||||
currentlyStartedActivities--
|
||||
isInBackground = currentlyStartedActivities == 0
|
||||
}
|
||||
|
||||
override fun onActivitySaveInstanceState(
|
||||
activity: Activity,
|
||||
outState: Bundle
|
||||
) {}
|
||||
override fun onActivityDestroyed(activity: Activity) {
|
||||
if (isTrackActivitiesEnabled) {
|
||||
activityLog.add(
|
||||
dateFormat.format(Date())
|
||||
.toString() + ": " + activity.javaClass
|
||||
.simpleName + " destroyed\n"
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Log.i(TAG, "UCEHandler has been installed.")
|
||||
} else {
|
||||
Log.e(TAG, "Context can not be null")
|
||||
}
|
||||
} catch (throwable: Throwable) {
|
||||
Log.e(
|
||||
TAG,
|
||||
"UCEHandler can not be initialized. Help making it better by reporting this as a bug.",
|
||||
throwable
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL method that tells if the app has crashed in the last seconds.
|
||||
* This is used to avoid restart loops.
|
||||
*
|
||||
* @return true if the app has crashed in the last seconds, false otherwise.
|
||||
*/
|
||||
private fun hasCrashedInTheLastSeconds(context: Context): Boolean {
|
||||
val lastTimestamp = getLastCrashTimestamp(context)
|
||||
val currentTimestamp: Long = Date().getTime()
|
||||
return lastTimestamp <= currentTimestamp && currentTimestamp - lastTimestamp < 3000
|
||||
}
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
private fun setLastCrashTimestamp(context: Context, timestamp: Long) {
|
||||
context.getSharedPreferences(SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE).edit()
|
||||
.putLong(SHARED_PREFERENCES_FIELD_TIMESTAMP, timestamp).commit()
|
||||
}
|
||||
|
||||
private fun killCurrentProcess() {
|
||||
// Process.killProcess(Process.myPid())
|
||||
exitProcess(10)
|
||||
}
|
||||
|
||||
private fun getLastCrashTimestamp(context: Context): Long {
|
||||
return context.getSharedPreferences(SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE)
|
||||
.getLong(SHARED_PREFERENCES_FIELD_TIMESTAMP, -1)
|
||||
}
|
||||
|
||||
fun closeApplication(activity: Activity) {
|
||||
activity.finish()
|
||||
killCurrentProcess()
|
||||
}
|
||||
|
||||
inner class Builder(context: Context) {
|
||||
val context: Context
|
||||
var isUCEHEnabled = true
|
||||
var commaSeparatedEmailAddresses: String? = null
|
||||
var isTrackActivitiesEnabled = false
|
||||
var isBackgroundModeEnabled = true
|
||||
fun setUCEHEnabled(isUCEHEnabled: Boolean): Builder {
|
||||
this.isUCEHEnabled = isUCEHEnabled
|
||||
return this
|
||||
}
|
||||
|
||||
fun setTrackActivitiesEnabled(isTrackActivitiesEnabled: Boolean): Builder {
|
||||
this.isTrackActivitiesEnabled = isTrackActivitiesEnabled
|
||||
return this
|
||||
}
|
||||
|
||||
fun setBackgroundModeEnabled(isBackgroundModeEnabled: Boolean): Builder {
|
||||
this.isBackgroundModeEnabled = isBackgroundModeEnabled
|
||||
return this
|
||||
}
|
||||
|
||||
fun addCommaSeparatedEmailAddresses(commaSeparatedEmailAddresses: String?): Builder {
|
||||
this.commaSeparatedEmailAddresses = commaSeparatedEmailAddresses ?: ""
|
||||
return this
|
||||
}
|
||||
|
||||
fun build() {
|
||||
return UCEHandler(this)
|
||||
}
|
||||
|
||||
init {
|
||||
this.context = context
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,137 +0,0 @@
|
||||
package com.hmg.hmgDr.model;
|
||||
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
|
||||
public class ChangeCallStatusRequestModel implements Parcelable {
|
||||
|
||||
@SerializedName("CallStatus")
|
||||
@Expose
|
||||
private Integer callStatus;
|
||||
@SerializedName("DoctorId")
|
||||
@Expose
|
||||
private Integer doctorId;
|
||||
@SerializedName("generalid")
|
||||
@Expose
|
||||
private String generalid;
|
||||
@SerializedName("TokenID")
|
||||
@Expose
|
||||
private String tokenID;
|
||||
@SerializedName("VC_ID")
|
||||
@Expose
|
||||
private Integer vcId;
|
||||
|
||||
public ChangeCallStatusRequestModel(Integer callStatus, Integer doctorId, String generalid, String tokenID, Integer vcId) {
|
||||
this.callStatus = callStatus;
|
||||
this.doctorId = doctorId;
|
||||
this.generalid = generalid;
|
||||
this.tokenID = tokenID;
|
||||
this.vcId = vcId;
|
||||
}
|
||||
|
||||
protected ChangeCallStatusRequestModel(Parcel in) {
|
||||
if (in.readByte() == 0) {
|
||||
callStatus = null;
|
||||
} else {
|
||||
callStatus = in.readInt();
|
||||
}
|
||||
if (in.readByte() == 0) {
|
||||
doctorId = null;
|
||||
} else {
|
||||
doctorId = in.readInt();
|
||||
}
|
||||
generalid = in.readString();
|
||||
tokenID = in.readString();
|
||||
if (in.readByte() == 0) {
|
||||
vcId = null;
|
||||
} else {
|
||||
vcId = in.readInt();
|
||||
}
|
||||
}
|
||||
|
||||
public static final Creator<ChangeCallStatusRequestModel> CREATOR = new Creator<ChangeCallStatusRequestModel>() {
|
||||
@Override
|
||||
public ChangeCallStatusRequestModel createFromParcel(Parcel in) {
|
||||
return new ChangeCallStatusRequestModel(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChangeCallStatusRequestModel[] newArray(int size) {
|
||||
return new ChangeCallStatusRequestModel[size];
|
||||
}
|
||||
};
|
||||
|
||||
public Integer getCallStatus() {
|
||||
return callStatus;
|
||||
}
|
||||
|
||||
public void setCallStatus(Integer callStatus) {
|
||||
this.callStatus = callStatus;
|
||||
}
|
||||
|
||||
public Integer getDoctorId() {
|
||||
return doctorId;
|
||||
}
|
||||
|
||||
public void setDoctorId(Integer doctorId) {
|
||||
this.doctorId = doctorId;
|
||||
}
|
||||
|
||||
public String getGeneralid() {
|
||||
return generalid;
|
||||
}
|
||||
|
||||
public void setGeneralid(String generalid) {
|
||||
this.generalid = generalid;
|
||||
}
|
||||
|
||||
public String getTokenID() {
|
||||
return tokenID;
|
||||
}
|
||||
|
||||
public void setTokenID(String tokenID) {
|
||||
this.tokenID = tokenID;
|
||||
}
|
||||
|
||||
public Integer getVcId() {
|
||||
return vcId;
|
||||
}
|
||||
|
||||
public void setVcId(Integer vcId) {
|
||||
this.vcId = vcId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
if (callStatus == null) {
|
||||
dest.writeByte((byte) 0);
|
||||
} else {
|
||||
dest.writeByte((byte) 1);
|
||||
dest.writeInt(callStatus);
|
||||
}
|
||||
if (doctorId == null) {
|
||||
dest.writeByte((byte) 0);
|
||||
} else {
|
||||
dest.writeByte((byte) 1);
|
||||
dest.writeInt(doctorId);
|
||||
}
|
||||
dest.writeString(generalid);
|
||||
dest.writeString(tokenID);
|
||||
if (vcId == null) {
|
||||
dest.writeByte((byte) 0);
|
||||
} else {
|
||||
dest.writeByte((byte) 1);
|
||||
dest.writeInt(vcId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
package com.hmg.hmgDr.model
|
||||
|
||||
/** Represents standard data needed for a Notification. */
|
||||
open class NotificationDataModel(
|
||||
// Standard notification values:
|
||||
var mContentTitle: String,
|
||||
var mContentText: String,
|
||||
var mPriority: Int ,
|
||||
// Notification channel values (O and above):
|
||||
var mChannelId: String,
|
||||
var mChannelName: CharSequence,
|
||||
var mChannelDescription: String,
|
||||
var mChannelImportance: Int ,
|
||||
var mChannelEnableVibrate: Boolean ,
|
||||
var mChannelLockscreenVisibility: Int
|
||||
)
|
||||
@ -1,35 +0,0 @@
|
||||
package com.hmg.hmgDr.model
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationManager
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationCompat
|
||||
|
||||
class NotificationVideoModel constructor(
|
||||
mContentTitle: String,
|
||||
mContentText: String,
|
||||
mChannelId: String,
|
||||
mChannelName: CharSequence,
|
||||
mChannelDescription: String,
|
||||
mPriority: Int = Notification.PRIORITY_MAX,
|
||||
mChannelImportance: Int = NotificationManager.IMPORTANCE_LOW,
|
||||
mChannelEnableVibrate: Boolean = true,
|
||||
mChannelLockscreenVisibility: Int = NotificationCompat.VISIBILITY_PUBLIC,
|
||||
// Unique data for this Notification.Style:
|
||||
var mBigContentTitle: String = mContentTitle,
|
||||
val mBigText: String = mContentText,
|
||||
var mSummaryText: String
|
||||
) : NotificationDataModel(
|
||||
mContentTitle,
|
||||
mContentText,
|
||||
mPriority,
|
||||
mChannelId,
|
||||
mChannelName,
|
||||
mChannelDescription,
|
||||
mChannelImportance,
|
||||
mChannelEnableVibrate,
|
||||
mChannelLockscreenVisibility
|
||||
) {
|
||||
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
package com.hmg.hmgDr.ui;
|
||||
|
||||
import com.hmg.hmgDr.model.ChangeCallStatusRequestModel;
|
||||
import com.hmg.hmgDr.model.GetSessionStatusModel;
|
||||
import com.hmg.hmgDr.model.SessionStatusModel;
|
||||
|
||||
public interface VideoCallContract {
|
||||
|
||||
interface VideoCallView {
|
||||
|
||||
void onCallSuccessful(SessionStatusModel sessionStatusModel);
|
||||
|
||||
void onCallChangeCallStatusSuccessful(SessionStatusModel sessionStatusModel);
|
||||
|
||||
void onFailure();
|
||||
|
||||
}
|
||||
|
||||
interface VideoCallPresenter {
|
||||
|
||||
void callClintConnected(GetSessionStatusModel statusModel);
|
||||
|
||||
void callChangeCallStatus(ChangeCallStatusRequestModel statusModel);
|
||||
}
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
package com.hmg.hmgDr.ui
|
||||
|
||||
import android.content.Intent
|
||||
|
||||
interface VideoCallResponseListener {
|
||||
|
||||
fun onCallFinished(resultCode : Int, intent: Intent? = null)
|
||||
|
||||
fun errorHandle(message: String){}
|
||||
|
||||
fun minimizeVideoEvent(isMinimize : Boolean)
|
||||
|
||||
fun onBackHandle(){}
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
package com.hmg.hmgDr.util
|
||||
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
object DateUtils {
|
||||
|
||||
var simpleDateFormat: SimpleDateFormat = SimpleDateFormat("hh:mm:ss", Locale.ENGLISH)
|
||||
|
||||
fun differentDateTimeBetweenDateAndNow(firstDate: Date): String {
|
||||
val now: Date = Calendar.getInstance().time
|
||||
//1 minute = 60 seconds
|
||||
//1 hour = 60 x 60 = 3600
|
||||
//1 day = 3600 x 24 = 86400
|
||||
|
||||
var different: Long = now.time - firstDate.time
|
||||
|
||||
val secondsInMilli: Long = 1000
|
||||
val minutesInMilli = secondsInMilli * 60
|
||||
val hoursInMilli = minutesInMilli * 60
|
||||
val daysInMilli = hoursInMilli * 24
|
||||
|
||||
val elapsedDays = different / daysInMilli
|
||||
different %= daysInMilli
|
||||
|
||||
val elapsedHours = different / hoursInMilli
|
||||
different %= hoursInMilli
|
||||
|
||||
val elapsedMinutes = different / minutesInMilli
|
||||
different %= minutesInMilli
|
||||
|
||||
val elapsedSeconds = different / secondsInMilli
|
||||
|
||||
val format = "%1$02d:%2$02d" // two digits
|
||||
return String.format(format, elapsedMinutes, elapsedSeconds)
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,77 +0,0 @@
|
||||
package com.hmg.hmgDr.util
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.hmg.hmgDr.model.NotificationDataModel
|
||||
import com.hmg.hmgDr.model.NotificationVideoModel
|
||||
|
||||
object NotificationUtil {
|
||||
|
||||
fun createNotificationChannel(
|
||||
context: Context,
|
||||
notificationDataModel: NotificationDataModel
|
||||
): String {
|
||||
// The id of the channel.
|
||||
val channelId: String = notificationDataModel.mChannelId
|
||||
|
||||
// NotificationChannels are required for Notifications on O (API 26) and above.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// The user-visible name of the channel.
|
||||
val channelName: CharSequence = notificationDataModel.mChannelName
|
||||
// The user-visible description of the channel.
|
||||
val channelDescription: String = notificationDataModel.mChannelDescription
|
||||
val channelImportance: Int = notificationDataModel.mChannelImportance
|
||||
val channelEnableVibrate: Boolean = notificationDataModel.mChannelEnableVibrate
|
||||
val channelLockscreenVisibility: Int =
|
||||
notificationDataModel.mChannelLockscreenVisibility
|
||||
|
||||
// Initializes NotificationChannel.
|
||||
val notificationChannel = NotificationChannel(channelId, channelName, channelImportance)
|
||||
notificationChannel.description = channelDescription
|
||||
notificationChannel.lightColor = Color.BLUE
|
||||
notificationChannel.lockscreenVisibility = channelLockscreenVisibility
|
||||
// no vibration
|
||||
notificationChannel.vibrationPattern = longArrayOf(0)
|
||||
notificationChannel.enableVibration(channelEnableVibrate)
|
||||
|
||||
// Adds NotificationChannel to system. Attempting to create an existing notification
|
||||
// channel with its original values performs no operation, so it's safe to perform the
|
||||
// below sequence.
|
||||
val notificationManager =
|
||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.createNotificationChannel(notificationChannel)
|
||||
}
|
||||
return channelId
|
||||
}
|
||||
|
||||
fun setNotificationBigStyle(notificationData : NotificationVideoModel): NotificationCompat.BigTextStyle {
|
||||
return NotificationCompat.BigTextStyle() // Overrides ContentText in the big form of the template.
|
||||
.bigText(notificationData.mBigText) // Overrides ContentTitle in the big form of the template.
|
||||
.setBigContentTitle(notificationData.mBigContentTitle) // Summary line after the detail section in the big form of the template.
|
||||
// Note: To improve readability, don't overload the user with info. If Summary Text
|
||||
// doesn't add critical information, you should skip it.
|
||||
.setSummaryText(notificationData.mSummaryText)
|
||||
}
|
||||
|
||||
/**
|
||||
* IMPORTANT NOTE: You should not do this action unless the user takes an action to see your
|
||||
* Notifications like this sample demonstrates. Spamming users to re-enable your notifications
|
||||
* is a bad idea.
|
||||
*/
|
||||
fun openNotificationSettingsForApp(context: Context) {
|
||||
// Links to this app's notification settings.
|
||||
val intent = Intent()
|
||||
intent.action = "android.settings.APP_NOTIFICATION_SETTINGS"
|
||||
intent.putExtra("app_package", context.packageName)
|
||||
intent.putExtra("app_uid", context.applicationInfo.uid)
|
||||
|
||||
// for Android 8 and above
|
||||
intent.putExtra("android.provider.extra.APP_PACKAGE", context.packageName)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
@ -1,467 +0,0 @@
|
||||
package com.hmg.hmgDr.util.audio
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.media.AudioFormat
|
||||
import android.media.AudioManager
|
||||
import android.media.AudioRecord
|
||||
import android.media.AudioTrack
|
||||
import android.media.MediaRecorder.AudioSource
|
||||
import android.os.Process
|
||||
import android.util.Log
|
||||
|
||||
import com.opentok.android.BaseAudioDevice
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.concurrent.locks.Condition
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
|
||||
class CustomAudioDevice(context: Context) : BaseAudioDevice() {
|
||||
|
||||
private val m_context: Context = context
|
||||
private var m_audioTrack: AudioTrack? = null
|
||||
private var m_audioRecord: AudioRecord? = null
|
||||
|
||||
// Capture & render buffers
|
||||
private var m_playBuffer: ByteBuffer? = null
|
||||
private var m_recBuffer: ByteBuffer? = null
|
||||
private val m_tempBufPlay: ByteArray
|
||||
private val m_tempBufRec: ByteArray
|
||||
private val m_rendererLock: ReentrantLock = ReentrantLock(true)
|
||||
private val m_renderEvent: Condition = m_rendererLock.newCondition()
|
||||
|
||||
@Volatile
|
||||
private var m_isRendering = false
|
||||
|
||||
@Volatile
|
||||
private var m_shutdownRenderThread = false
|
||||
private val m_captureLock: ReentrantLock = ReentrantLock(true)
|
||||
private val m_captureEvent: Condition = m_captureLock.newCondition()
|
||||
|
||||
@Volatile
|
||||
private var m_isCapturing = false
|
||||
|
||||
@Volatile
|
||||
private var m_shutdownCaptureThread = false
|
||||
private val m_captureSettings: AudioSettings
|
||||
private val m_rendererSettings: AudioSettings
|
||||
|
||||
// Capturing delay estimation
|
||||
private var m_estimatedCaptureDelay = 0
|
||||
|
||||
// Rendering delay estimation
|
||||
private var m_bufferedPlaySamples = 0
|
||||
private var m_playPosition = 0
|
||||
private var m_estimatedRenderDelay = 0
|
||||
private val m_audioManager: AudioManager
|
||||
private var isRendererMuted = false
|
||||
|
||||
companion object {
|
||||
private const val LOG_TAG = "opentok-defaultaudio"
|
||||
private const val SAMPLING_RATE = 44100
|
||||
private const val NUM_CHANNELS_CAPTURING = 1
|
||||
private const val NUM_CHANNELS_RENDERING = 1
|
||||
private const val MAX_SAMPLES = 2 * 480 * 2 // Max 10 ms @ 48 kHz
|
||||
}
|
||||
|
||||
init {
|
||||
try {
|
||||
m_playBuffer = ByteBuffer.allocateDirect(MAX_SAMPLES)
|
||||
m_recBuffer = ByteBuffer.allocateDirect(MAX_SAMPLES)
|
||||
} catch (e: Exception) {
|
||||
Log.e(LOG_TAG, "${e.message}.")
|
||||
}
|
||||
m_tempBufPlay = ByteArray(MAX_SAMPLES)
|
||||
m_tempBufRec = ByteArray(MAX_SAMPLES)
|
||||
m_captureSettings = AudioSettings(
|
||||
SAMPLING_RATE,
|
||||
NUM_CHANNELS_CAPTURING
|
||||
)
|
||||
m_rendererSettings = AudioSettings(
|
||||
SAMPLING_RATE,
|
||||
NUM_CHANNELS_RENDERING
|
||||
)
|
||||
m_audioManager = m_context
|
||||
.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
m_audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
|
||||
}
|
||||
|
||||
override fun initCapturer(): Boolean {
|
||||
|
||||
// get the minimum buffer size that can be used
|
||||
val minRecBufSize: Int = AudioRecord.getMinBufferSize(
|
||||
m_captureSettings
|
||||
.sampleRate,
|
||||
if (NUM_CHANNELS_CAPTURING == 1) AudioFormat.CHANNEL_IN_MONO else AudioFormat.CHANNEL_IN_STEREO,
|
||||
AudioFormat.ENCODING_PCM_16BIT
|
||||
)
|
||||
|
||||
// double size to be more safe
|
||||
val recBufSize = minRecBufSize * 2
|
||||
|
||||
// release the object
|
||||
if (m_audioRecord != null) {
|
||||
m_audioRecord!!.release()
|
||||
m_audioRecord = null
|
||||
}
|
||||
try {
|
||||
m_audioRecord = AudioRecord(
|
||||
AudioSource.VOICE_COMMUNICATION,
|
||||
m_captureSettings.sampleRate,
|
||||
if (NUM_CHANNELS_CAPTURING == 1) AudioFormat.CHANNEL_IN_MONO else AudioFormat.CHANNEL_IN_STEREO,
|
||||
AudioFormat.ENCODING_PCM_16BIT, recBufSize
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(LOG_TAG, "${e.message}.")
|
||||
return false
|
||||
}
|
||||
|
||||
// check that the audioRecord is ready to be used
|
||||
if (m_audioRecord!!.state != AudioRecord.STATE_INITIALIZED) {
|
||||
Log.i(
|
||||
LOG_TAG, "Audio capture is not initialized "
|
||||
+ m_captureSettings.sampleRate
|
||||
)
|
||||
return false
|
||||
}
|
||||
m_shutdownCaptureThread = false
|
||||
Thread(m_captureThread).start()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun destroyCapturer(): Boolean {
|
||||
m_captureLock.lock()
|
||||
// release the object
|
||||
m_audioRecord?.release()
|
||||
m_audioRecord = null
|
||||
m_shutdownCaptureThread = true
|
||||
m_captureEvent.signal()
|
||||
m_captureLock.unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getEstimatedCaptureDelay(): Int {
|
||||
return m_estimatedCaptureDelay
|
||||
}
|
||||
|
||||
override fun startCapturer(): Boolean {
|
||||
// start recording
|
||||
try {
|
||||
m_audioRecord!!.startRecording()
|
||||
} catch (e: IllegalStateException) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
m_captureLock.lock()
|
||||
m_isCapturing = true
|
||||
m_captureEvent.signal()
|
||||
m_captureLock.unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun stopCapturer(): Boolean {
|
||||
m_captureLock.lock()
|
||||
try {
|
||||
// only stop if we are recording
|
||||
if (m_audioRecord!!.recordingState == AudioRecord.RECORDSTATE_RECORDING) {
|
||||
// stop recording
|
||||
try {
|
||||
m_audioRecord!!.stop()
|
||||
} catch (e: IllegalStateException) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// Ensure we always unlock
|
||||
m_isCapturing = false
|
||||
m_captureLock.unlock()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private val m_captureThread = Runnable {
|
||||
val samplesToRec = SAMPLING_RATE / 100
|
||||
var samplesRead = 0
|
||||
try {
|
||||
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
while (!m_shutdownCaptureThread) {
|
||||
m_captureLock.lock()
|
||||
samplesRead = try {
|
||||
if (!m_isCapturing) {
|
||||
m_captureEvent.await()
|
||||
continue
|
||||
} else {
|
||||
if (m_audioRecord == null) {
|
||||
continue
|
||||
}
|
||||
val lengthInBytes = ((samplesToRec shl 1)
|
||||
* NUM_CHANNELS_CAPTURING)
|
||||
val readBytes: Int = m_audioRecord!!.read(
|
||||
m_tempBufRec, 0,
|
||||
lengthInBytes
|
||||
)
|
||||
m_recBuffer!!.rewind()
|
||||
m_recBuffer!!.put(m_tempBufRec)
|
||||
(readBytes shr 1) / NUM_CHANNELS_CAPTURING
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(LOG_TAG, "RecordAudio try failed: " + e.message)
|
||||
continue
|
||||
} finally {
|
||||
// Ensure we always unlock
|
||||
m_captureLock.unlock()
|
||||
}
|
||||
audioBus.writeCaptureData(m_recBuffer, samplesRead)
|
||||
m_estimatedCaptureDelay = samplesRead * 1000 / SAMPLING_RATE
|
||||
}
|
||||
}
|
||||
|
||||
override fun initRenderer(): Boolean {
|
||||
|
||||
// get the minimum buffer size that can be used
|
||||
val minPlayBufSize: Int = AudioTrack.getMinBufferSize(
|
||||
m_rendererSettings
|
||||
.sampleRate,
|
||||
if (NUM_CHANNELS_RENDERING == 1) AudioFormat.CHANNEL_OUT_MONO else AudioFormat.CHANNEL_OUT_STEREO,
|
||||
AudioFormat.ENCODING_PCM_16BIT
|
||||
)
|
||||
var playBufSize = minPlayBufSize
|
||||
if (playBufSize < 6000) {
|
||||
playBufSize *= 2
|
||||
}
|
||||
|
||||
// release the object
|
||||
if (m_audioTrack != null) {
|
||||
m_audioTrack!!.release()
|
||||
m_audioTrack = null
|
||||
}
|
||||
try {
|
||||
m_audioTrack = AudioTrack(
|
||||
AudioManager.STREAM_VOICE_CALL,
|
||||
m_rendererSettings.sampleRate,
|
||||
if (NUM_CHANNELS_RENDERING == 1) AudioFormat.CHANNEL_OUT_MONO else AudioFormat.CHANNEL_OUT_STEREO,
|
||||
AudioFormat.ENCODING_PCM_16BIT, playBufSize,
|
||||
AudioTrack.MODE_STREAM
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(LOG_TAG, "${e.message}.")
|
||||
return false
|
||||
}
|
||||
|
||||
// check that the audioRecord is ready to be used
|
||||
if (m_audioTrack!!.state != AudioTrack.STATE_INITIALIZED) {
|
||||
Log.i(
|
||||
LOG_TAG, "Audio renderer not initialized "
|
||||
+ m_rendererSettings.sampleRate
|
||||
)
|
||||
return false
|
||||
}
|
||||
m_bufferedPlaySamples = 0
|
||||
outputMode = OutputMode.SpeakerPhone
|
||||
m_shutdownRenderThread = false
|
||||
Thread(m_renderThread).start()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun destroyRenderer(): Boolean {
|
||||
m_rendererLock.lock()
|
||||
// release the object
|
||||
m_audioTrack!!.release()
|
||||
m_audioTrack = null
|
||||
m_shutdownRenderThread = true
|
||||
m_renderEvent.signal()
|
||||
m_rendererLock.unlock()
|
||||
unregisterHeadsetReceiver()
|
||||
m_audioManager.isSpeakerphoneOn = false
|
||||
m_audioManager.mode = AudioManager.MODE_NORMAL
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getEstimatedRenderDelay(): Int {
|
||||
return m_estimatedRenderDelay
|
||||
}
|
||||
|
||||
override fun startRenderer(): Boolean {
|
||||
// start playout
|
||||
try {
|
||||
m_audioTrack!!.play()
|
||||
} catch (e: IllegalStateException) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
m_rendererLock.lock()
|
||||
m_isRendering = true
|
||||
m_renderEvent.signal()
|
||||
m_rendererLock.unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun stopRenderer(): Boolean {
|
||||
m_rendererLock.lock()
|
||||
try {
|
||||
// only stop if we are playing
|
||||
if (m_audioTrack!!.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
|
||||
// stop playout
|
||||
try {
|
||||
m_audioTrack!!.stop()
|
||||
} catch (e: IllegalStateException) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
|
||||
// flush the buffers
|
||||
m_audioTrack!!.flush()
|
||||
}
|
||||
} finally {
|
||||
// Ensure we always unlock, both for success, exception or error
|
||||
// return.
|
||||
m_isRendering = false
|
||||
m_rendererLock.unlock()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private val m_renderThread = Runnable {
|
||||
val samplesToPlay = SAMPLING_RATE / 100
|
||||
try {
|
||||
Process
|
||||
.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
while (!m_shutdownRenderThread) {
|
||||
m_rendererLock.lock()
|
||||
try {
|
||||
if (!m_isRendering) {
|
||||
m_renderEvent.await()
|
||||
continue
|
||||
} else {
|
||||
m_rendererLock.unlock()
|
||||
|
||||
// Don't lock on audioBus calls
|
||||
m_playBuffer!!.clear()
|
||||
val samplesRead: Int = audioBus.readRenderData(
|
||||
m_playBuffer, samplesToPlay
|
||||
)
|
||||
|
||||
// Log.d(LOG_TAG, "Samples read: " + samplesRead);
|
||||
m_rendererLock.lock()
|
||||
if (!isRendererMuted) {
|
||||
// After acquiring the lock again
|
||||
// we must check if we are still playing
|
||||
if (m_audioTrack == null
|
||||
|| !m_isRendering
|
||||
) {
|
||||
continue
|
||||
}
|
||||
val bytesRead = ((samplesRead shl 1)
|
||||
* NUM_CHANNELS_RENDERING)
|
||||
m_playBuffer!!.get(m_tempBufPlay, 0, bytesRead)
|
||||
val bytesWritten: Int = m_audioTrack!!.write(
|
||||
m_tempBufPlay, 0,
|
||||
bytesRead
|
||||
)
|
||||
|
||||
// increase by number of written samples
|
||||
m_bufferedPlaySamples += ((bytesWritten shr 1)
|
||||
/ NUM_CHANNELS_RENDERING)
|
||||
|
||||
// decrease by number of played samples
|
||||
val pos: Int = m_audioTrack!!.getPlaybackHeadPosition()
|
||||
if (pos < m_playPosition) {
|
||||
// wrap or reset by driver
|
||||
m_playPosition = 0
|
||||
}
|
||||
m_bufferedPlaySamples -= pos - m_playPosition
|
||||
m_playPosition = pos
|
||||
|
||||
// we calculate the estimated delay based on the
|
||||
// buffered samples
|
||||
m_estimatedRenderDelay = (m_bufferedPlaySamples * 1000
|
||||
/ SAMPLING_RATE)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(LOG_TAG, "Exception: " + e.message)
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
m_rendererLock.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCaptureSettings(): AudioSettings {
|
||||
return m_captureSettings
|
||||
}
|
||||
|
||||
override fun getRenderSettings(): AudioSettings {
|
||||
return m_rendererSettings
|
||||
}
|
||||
|
||||
/**
|
||||
* Communication modes handling
|
||||
*/
|
||||
override fun setOutputMode(mode: OutputMode): Boolean {
|
||||
super.setOutputMode(mode)
|
||||
if (mode == OutputMode.Handset) {
|
||||
unregisterHeadsetReceiver()
|
||||
m_audioManager.isSpeakerphoneOn = false
|
||||
} else {
|
||||
m_audioManager.isSpeakerphoneOn = true
|
||||
registerHeadsetReceiver()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private val m_headsetReceiver: BroadcastReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent) {
|
||||
if (intent.action!!.compareTo(Intent.ACTION_HEADSET_PLUG) == 0) {
|
||||
val state: Int = intent.getIntExtra("state", 0)
|
||||
m_audioManager.isSpeakerphoneOn = state == 0
|
||||
}
|
||||
}
|
||||
}
|
||||
private var m_receiverRegistered = false
|
||||
private fun registerHeadsetReceiver() {
|
||||
if (!m_receiverRegistered) {
|
||||
val receiverFilter = IntentFilter(
|
||||
Intent.ACTION_HEADSET_PLUG
|
||||
)
|
||||
m_context.registerReceiver(m_headsetReceiver, receiverFilter)
|
||||
m_receiverRegistered = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun unregisterHeadsetReceiver() {
|
||||
if (m_receiverRegistered) {
|
||||
try {
|
||||
m_context.unregisterReceiver(m_headsetReceiver)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
m_receiverRegistered = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
if (outputMode == OutputMode.SpeakerPhone) {
|
||||
unregisterHeadsetReceiver()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
if (outputMode == OutputMode.SpeakerPhone) {
|
||||
registerHeadsetReceiver()
|
||||
}
|
||||
}
|
||||
|
||||
fun setRendererMute(isRendererMuted: Boolean) {
|
||||
this.isRendererMuted = isRendererMuted
|
||||
}
|
||||
}
|
||||
@ -1,379 +0,0 @@
|
||||
package com.hmg.hmgDr.util.opentok
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.graphics.PixelFormat
|
||||
import android.opengl.GLES20
|
||||
import android.opengl.GLSurfaceView
|
||||
import android.opengl.Matrix
|
||||
import android.view.View
|
||||
import com.opentok.android.BaseVideoRenderer
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.nio.FloatBuffer
|
||||
import java.nio.ShortBuffer
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import javax.microedition.khronos.egl.EGLConfig
|
||||
import javax.microedition.khronos.opengles.GL10
|
||||
|
||||
/*
|
||||
* https://nhancv.medium.com/android-how-to-make-a-circular-view-as-a-thumbnail-of-opentok-27992aee15c9
|
||||
* to solve make circle video stream
|
||||
* */
|
||||
|
||||
class DynamicVideoRenderer(private val mContext: Context) : BaseVideoRenderer() {
|
||||
private val mView: GLSurfaceView = GLSurfaceView(mContext)
|
||||
private val mRenderer: MyRenderer
|
||||
|
||||
interface DynamicVideoRendererMetadataListener {
|
||||
fun onMetadataReady(metadata: ByteArray?)
|
||||
}
|
||||
|
||||
fun setDynamicVideoRendererMetadataListener(metadataListener: DynamicVideoRendererMetadataListener?) {
|
||||
mRenderer.metadataListener = metadataListener
|
||||
}
|
||||
|
||||
fun enableThumbnailCircle(enable: Boolean) {
|
||||
mRenderer.requestEnableThumbnailCircle = enable
|
||||
}
|
||||
|
||||
internal class MyRenderer : GLSurfaceView.Renderer {
|
||||
var mTextureIds = IntArray(3)
|
||||
var mScaleMatrix = FloatArray(16)
|
||||
private val mVertexBuffer: FloatBuffer
|
||||
private val mTextureBuffer: FloatBuffer
|
||||
private val mDrawListBuffer: ShortBuffer
|
||||
var requestEnableThumbnailCircle = false
|
||||
var mVideoFitEnabled = true
|
||||
var mVideoDisabled = false
|
||||
private val mVertexIndex = shortArrayOf(0, 1, 2, 0, 2, 3) // order to draw
|
||||
|
||||
// vertices
|
||||
private val vertexShaderCode = """uniform mat4 uMVPMatrix;attribute vec4 aPosition;
|
||||
attribute vec2 aTextureCoord;
|
||||
varying vec2 vTextureCoord;
|
||||
void main() {
|
||||
gl_Position = uMVPMatrix * aPosition;
|
||||
vTextureCoord = aTextureCoord;
|
||||
}
|
||||
"""
|
||||
private val fragmentShaderCode = """precision mediump float;
|
||||
uniform sampler2D Ytex;
|
||||
uniform sampler2D Utex,Vtex;
|
||||
uniform int enableCircle;
|
||||
uniform vec2 radiusDp;
|
||||
varying vec2 vTextureCoord;
|
||||
void main(void) {
|
||||
float nx,ny,r,g,b,y,u,v;
|
||||
mediump vec4 txl,ux,vx; nx=vTextureCoord[0];
|
||||
ny=vTextureCoord[1];
|
||||
y=texture2D(Ytex,vec2(nx,ny)).r;
|
||||
u=texture2D(Utex,vec2(nx,ny)).r;
|
||||
v=texture2D(Vtex,vec2(nx,ny)).r;
|
||||
y=1.1643*(y-0.0625);
|
||||
u=u-0.5;
|
||||
v=v-0.5;
|
||||
r=y+1.5958*v;
|
||||
g=y-0.39173*u-0.81290*v;
|
||||
b=y+2.017*u;
|
||||
if (enableCircle > 0) {
|
||||
float radius = 0.5;
|
||||
vec4 color0 = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 color1 = vec4(r, g, b, 1.0);
|
||||
vec2 st = (gl_FragCoord.xy/radiusDp.xy); float dist = radius - distance(st,vec2(0.5));
|
||||
float t = 1.0;
|
||||
if (dist < 0.0) t = 0.0;
|
||||
gl_FragColor = mix(color0, color1, t);
|
||||
}
|
||||
else {
|
||||
gl_FragColor = vec4(r, g, b, 1.0);
|
||||
}
|
||||
}
|
||||
"""
|
||||
var mFrameLock = ReentrantLock()
|
||||
var mCurrentFrame: Frame? = null
|
||||
private var mProgram = 0
|
||||
private var mTextureWidth = 0
|
||||
private var mTextureHeight = 0
|
||||
private var mViewportWidth = 0
|
||||
private var mViewportHeight = 0
|
||||
override fun onSurfaceCreated(gl: GL10, config: EGLConfig) {
|
||||
gl.glClearColor(0f, 0f, 0f, 1f)
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
||||
val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,
|
||||
vertexShaderCode)
|
||||
val fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
|
||||
fragmentShaderCode)
|
||||
mProgram = GLES20.glCreateProgram() // create empty OpenGL ES
|
||||
// Program
|
||||
GLES20.glAttachShader(mProgram, vertexShader) // add the vertex
|
||||
// shader to program
|
||||
GLES20.glAttachShader(mProgram, fragmentShader) // add the fragment
|
||||
// shader to
|
||||
// program
|
||||
GLES20.glLinkProgram(mProgram)
|
||||
val positionHandle = GLES20.glGetAttribLocation(mProgram,
|
||||
"aPosition")
|
||||
val textureHandle = GLES20.glGetAttribLocation(mProgram,
|
||||
"aTextureCoord")
|
||||
GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
|
||||
GLES20.GL_FLOAT, false, COORDS_PER_VERTEX * 4,
|
||||
mVertexBuffer)
|
||||
GLES20.glEnableVertexAttribArray(positionHandle)
|
||||
GLES20.glVertexAttribPointer(textureHandle,
|
||||
TEXTURECOORDS_PER_VERTEX, GLES20.GL_FLOAT, false,
|
||||
TEXTURECOORDS_PER_VERTEX * 4, mTextureBuffer)
|
||||
GLES20.glEnableVertexAttribArray(textureHandle)
|
||||
GLES20.glUseProgram(mProgram)
|
||||
var i = GLES20.glGetUniformLocation(mProgram, "Ytex")
|
||||
GLES20.glUniform1i(i, 0) /* Bind Ytex to texture unit 0 */
|
||||
i = GLES20.glGetUniformLocation(mProgram, "Utex")
|
||||
GLES20.glUniform1i(i, 1) /* Bind Utex to texture unit 1 */
|
||||
i = GLES20.glGetUniformLocation(mProgram, "Vtex")
|
||||
GLES20.glUniform1i(i, 2) /* Bind Vtex to texture unit 2 */
|
||||
val radiusDpLocation = GLES20.glGetUniformLocation(mProgram, "radiusDp")
|
||||
val radiusDp = (Resources.getSystem().displayMetrics.density * THUMBNAIL_SIZE).toInt()
|
||||
GLES20.glUniform2f(radiusDpLocation, radiusDp.toFloat(), radiusDp.toFloat())
|
||||
mTextureWidth = 0
|
||||
mTextureHeight = 0
|
||||
}
|
||||
|
||||
fun enableThumbnailCircle(enable: Boolean) {
|
||||
GLES20.glUseProgram(mProgram)
|
||||
val enableCircleLocation = GLES20.glGetUniformLocation(mProgram, "enableCircle")
|
||||
GLES20.glUniform1i(enableCircleLocation, if (enable) 1 else 0)
|
||||
}
|
||||
|
||||
fun setupTextures(frame: Frame) {
|
||||
if (mTextureIds[0] != 0) {
|
||||
GLES20.glDeleteTextures(3, mTextureIds, 0)
|
||||
}
|
||||
GLES20.glGenTextures(3, mTextureIds, 0)
|
||||
val w = frame.width
|
||||
val h = frame.height
|
||||
val hw = w + 1 shr 1
|
||||
val hh = h + 1 shr 1
|
||||
initializeTexture(GLES20.GL_TEXTURE0, mTextureIds[0], w, h)
|
||||
initializeTexture(GLES20.GL_TEXTURE1, mTextureIds[1], hw, hh)
|
||||
initializeTexture(GLES20.GL_TEXTURE2, mTextureIds[2], hw, hh)
|
||||
mTextureWidth = frame.width
|
||||
mTextureHeight = frame.height
|
||||
}
|
||||
|
||||
fun updateTextures(frame: Frame) {
|
||||
val width = frame.width
|
||||
val height = frame.height
|
||||
val half_width = width + 1 shr 1
|
||||
val half_height = height + 1 shr 1
|
||||
val y_size = width * height
|
||||
val uv_size = half_width * half_height
|
||||
val bb = frame.buffer
|
||||
// If we are reusing this frame, make sure we reset position and
|
||||
// limit
|
||||
bb.clear()
|
||||
if (bb.remaining() == y_size + uv_size * 2) {
|
||||
bb.position(0)
|
||||
GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1)
|
||||
GLES20.glPixelStorei(GLES20.GL_PACK_ALIGNMENT, 1)
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[0])
|
||||
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, width,
|
||||
height, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE,
|
||||
bb)
|
||||
bb.position(y_size)
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE1)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[1])
|
||||
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0,
|
||||
half_width, half_height, GLES20.GL_LUMINANCE,
|
||||
GLES20.GL_UNSIGNED_BYTE, bb)
|
||||
bb.position(y_size + uv_size)
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE2)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[2])
|
||||
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0,
|
||||
half_width, half_height, GLES20.GL_LUMINANCE,
|
||||
GLES20.GL_UNSIGNED_BYTE, bb)
|
||||
} else {
|
||||
mTextureWidth = 0
|
||||
mTextureHeight = 0
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) {
|
||||
GLES20.glViewport(0, 0, width, height)
|
||||
mViewportWidth = width
|
||||
mViewportHeight = height
|
||||
}
|
||||
|
||||
var metadataListener: DynamicVideoRendererMetadataListener? = null
|
||||
override fun onDrawFrame(gl: GL10) {
|
||||
gl.glClearColor(0f, 0f, 0f, 0f)
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
||||
mFrameLock.lock()
|
||||
if (mCurrentFrame != null && !mVideoDisabled) {
|
||||
GLES20.glUseProgram(mProgram)
|
||||
if (mTextureWidth != mCurrentFrame!!.width
|
||||
|| mTextureHeight != mCurrentFrame!!.height) {
|
||||
setupTextures(mCurrentFrame!!)
|
||||
}
|
||||
updateTextures(mCurrentFrame!!)
|
||||
Matrix.setIdentityM(mScaleMatrix, 0)
|
||||
var scaleX = 1.0f
|
||||
var scaleY = 1.0f
|
||||
val ratio = (mCurrentFrame!!.width.toFloat()
|
||||
/ mCurrentFrame!!.height)
|
||||
val vratio = mViewportWidth.toFloat() / mViewportHeight
|
||||
if (mVideoFitEnabled) {
|
||||
if (ratio > vratio) {
|
||||
scaleY = vratio / ratio
|
||||
} else {
|
||||
scaleX = ratio / vratio
|
||||
}
|
||||
} else {
|
||||
if (ratio < vratio) {
|
||||
scaleY = vratio / ratio
|
||||
} else {
|
||||
scaleX = ratio / vratio
|
||||
}
|
||||
}
|
||||
Matrix.scaleM(mScaleMatrix, 0,
|
||||
scaleX * if (mCurrentFrame!!.isMirroredX) -1.0f else 1.0f,
|
||||
scaleY, 1f)
|
||||
metadataListener?.onMetadataReady(mCurrentFrame!!.metadata)
|
||||
val mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram,
|
||||
"uMVPMatrix")
|
||||
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false,
|
||||
mScaleMatrix, 0)
|
||||
enableThumbnailCircle(requestEnableThumbnailCircle)
|
||||
GLES20.glDrawElements(GLES20.GL_TRIANGLES, mVertexIndex.size,
|
||||
GLES20.GL_UNSIGNED_SHORT, mDrawListBuffer)
|
||||
} else {
|
||||
//black frame when video is disabled
|
||||
gl.glClearColor(0f, 0f, 0f, 1f)
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
||||
}
|
||||
mFrameLock.unlock()
|
||||
}
|
||||
|
||||
fun displayFrame(frame: Frame?) {
|
||||
mFrameLock.lock()
|
||||
if (mCurrentFrame != null) {
|
||||
mCurrentFrame!!.recycle()
|
||||
}
|
||||
mCurrentFrame = frame
|
||||
mFrameLock.unlock()
|
||||
}
|
||||
|
||||
fun disableVideo(b: Boolean) {
|
||||
mFrameLock.lock()
|
||||
mVideoDisabled = b
|
||||
if (mVideoDisabled) {
|
||||
if (mCurrentFrame != null) {
|
||||
mCurrentFrame!!.recycle()
|
||||
}
|
||||
mCurrentFrame = null
|
||||
}
|
||||
mFrameLock.unlock()
|
||||
}
|
||||
|
||||
fun enableVideoFit(enableVideoFit: Boolean) {
|
||||
mVideoFitEnabled = enableVideoFit
|
||||
}
|
||||
|
||||
companion object {
|
||||
// number of coordinates per vertex in this array
|
||||
const val COORDS_PER_VERTEX = 3
|
||||
const val TEXTURECOORDS_PER_VERTEX = 2
|
||||
var mXYZCoords = floatArrayOf(
|
||||
-1.0f, 1.0f, 0.0f, // top left
|
||||
-1.0f, -1.0f, 0.0f, // bottom left
|
||||
1.0f, -1.0f, 0.0f, // bottom right
|
||||
1.0f, 1.0f, 0.0f // top right
|
||||
)
|
||||
var mUVCoords = floatArrayOf(0f, 0f, 0f, 1f, 1f, 1f, 1f, 0f)
|
||||
fun initializeTexture(name: Int, id: Int, width: Int, height: Int) {
|
||||
GLES20.glActiveTexture(name)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, id)
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST.toFloat())
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR.toFloat())
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE.toFloat())
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE.toFloat())
|
||||
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE,
|
||||
width, height, 0, GLES20.GL_LUMINANCE,
|
||||
GLES20.GL_UNSIGNED_BYTE, null)
|
||||
}
|
||||
|
||||
fun loadShader(type: Int, shaderCode: String?): Int {
|
||||
val shader = GLES20.glCreateShader(type)
|
||||
GLES20.glShaderSource(shader, shaderCode)
|
||||
GLES20.glCompileShader(shader)
|
||||
return shader
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
val bb = ByteBuffer.allocateDirect(mXYZCoords.size * 4)
|
||||
bb.order(ByteOrder.nativeOrder())
|
||||
mVertexBuffer = bb.asFloatBuffer()
|
||||
mVertexBuffer.put(mXYZCoords)
|
||||
mVertexBuffer.position(0)
|
||||
val tb = ByteBuffer.allocateDirect(mUVCoords.size * 4)
|
||||
tb.order(ByteOrder.nativeOrder())
|
||||
mTextureBuffer = tb.asFloatBuffer()
|
||||
mTextureBuffer.put(mUVCoords)
|
||||
mTextureBuffer.position(0)
|
||||
val dlb = ByteBuffer.allocateDirect(mVertexIndex.size * 2)
|
||||
dlb.order(ByteOrder.nativeOrder())
|
||||
mDrawListBuffer = dlb.asShortBuffer()
|
||||
mDrawListBuffer.put(mVertexIndex)
|
||||
mDrawListBuffer.position(0)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFrame(frame: Frame) {
|
||||
mRenderer.displayFrame(frame)
|
||||
mView.requestRender()
|
||||
}
|
||||
|
||||
override fun setStyle(key: String, value: String) {
|
||||
if (STYLE_VIDEO_SCALE == key) {
|
||||
if (STYLE_VIDEO_FIT == value) {
|
||||
mRenderer.enableVideoFit(true)
|
||||
} else if (STYLE_VIDEO_FILL == value) {
|
||||
mRenderer.enableVideoFit(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onVideoPropertiesChanged(videoEnabled: Boolean) {
|
||||
mRenderer.disableVideo(!videoEnabled)
|
||||
}
|
||||
|
||||
override fun getView(): View {
|
||||
return mView
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
mView.onPause()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
mView.onResume()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val THUMBNAIL_SIZE = 90 //in dp
|
||||
}
|
||||
|
||||
init {
|
||||
mView.setEGLContextClientVersion(2)
|
||||
mView.setEGLConfigChooser(8, 8, 8, 8, 16, 0)
|
||||
mView.holder.setFormat(PixelFormat.TRANSLUCENT)
|
||||
mRenderer = MyRenderer()
|
||||
mView.setRenderer(mRenderer)
|
||||
mView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY
|
||||
}
|
||||
}
|
||||
@ -1,357 +0,0 @@
|
||||
package com.hmg.hmgDr.util.opentok
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.graphics.PixelFormat
|
||||
import android.opengl.GLES20
|
||||
import android.opengl.GLSurfaceView
|
||||
import android.opengl.Matrix
|
||||
import android.view.View
|
||||
import com.opentok.android.BaseVideoRenderer
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.nio.FloatBuffer
|
||||
import java.nio.ShortBuffer
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import javax.microedition.khronos.egl.EGLConfig
|
||||
import javax.microedition.khronos.opengles.GL10
|
||||
|
||||
|
||||
class ThumbnailCircleVideoRenderer(private val mContext: Context) : BaseVideoRenderer() {
|
||||
private val mView: GLSurfaceView = GLSurfaceView(mContext)
|
||||
private val mRenderer: MyRenderer
|
||||
|
||||
interface ThumbnailCircleVideoRendererMetadataListener {
|
||||
fun onMetadataReady(metadata: ByteArray?)
|
||||
}
|
||||
|
||||
fun setThumbnailCircleVideoRendererMetadataListener(metadataListener: ThumbnailCircleVideoRendererMetadataListener?) {
|
||||
mRenderer.metadataListener = metadataListener
|
||||
}
|
||||
|
||||
internal class MyRenderer : GLSurfaceView.Renderer {
|
||||
var mTextureIds = IntArray(3)
|
||||
var mScaleMatrix = FloatArray(16)
|
||||
private val mVertexBuffer: FloatBuffer
|
||||
private val mTextureBuffer: FloatBuffer
|
||||
private val mDrawListBuffer: ShortBuffer
|
||||
var mVideoFitEnabled = true
|
||||
var mVideoDisabled = false
|
||||
private val mVertexIndex = shortArrayOf(0, 1, 2, 0, 2, 3) // order to draw
|
||||
|
||||
// vertices
|
||||
private val vertexShaderCode = """uniform mat4 uMVPMatrix;attribute vec4 aPosition;
|
||||
attribute vec2 aTextureCoord;
|
||||
varying vec2 vTextureCoord;
|
||||
void main() {
|
||||
gl_Position = uMVPMatrix * aPosition;
|
||||
vTextureCoord = aTextureCoord;
|
||||
}
|
||||
"""
|
||||
private val fragmentShaderCode = """precision mediump float;
|
||||
uniform sampler2D Ytex;
|
||||
uniform sampler2D Utex,Vtex;
|
||||
uniform vec2 radiusDp;
|
||||
varying vec2 vTextureCoord;
|
||||
void main(void) {
|
||||
float nx,ny,r,g,b,y,u,v;
|
||||
mediump vec4 txl,ux,vx; nx=vTextureCoord[0];
|
||||
ny=vTextureCoord[1];
|
||||
y=texture2D(Ytex,vec2(nx,ny)).r;
|
||||
u=texture2D(Utex,vec2(nx,ny)).r;
|
||||
v=texture2D(Vtex,vec2(nx,ny)).r;
|
||||
y=1.1643*(y-0.0625);
|
||||
u=u-0.5;
|
||||
v=v-0.5;
|
||||
r=y+1.5958*v;
|
||||
g=y-0.39173*u-0.81290*v;
|
||||
b=y+2.017*u;
|
||||
float radius = 0.5;
|
||||
vec4 color0 = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 color1 = vec4(r, g, b, 1.0);
|
||||
vec2 st = (gl_FragCoord.xy/radiusDp.xy); float dist = radius - distance(st,vec2(0.5));
|
||||
float t = 1.0;
|
||||
if (dist < 0.0) t = 0.0;
|
||||
gl_FragColor = mix(color0, color1, t);
|
||||
}
|
||||
"""
|
||||
var mFrameLock = ReentrantLock()
|
||||
var mCurrentFrame: Frame? = null
|
||||
private var mProgram = 0
|
||||
private var mTextureWidth = 0
|
||||
private var mTextureHeight = 0
|
||||
private var mViewportWidth = 0
|
||||
private var mViewportHeight = 0
|
||||
override fun onSurfaceCreated(gl: GL10, config: EGLConfig) {
|
||||
gl.glClearColor(0f, 0f, 0f, 1f)
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
||||
val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,
|
||||
vertexShaderCode)
|
||||
val fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
|
||||
fragmentShaderCode)
|
||||
mProgram = GLES20.glCreateProgram() // create empty OpenGL ES
|
||||
// Program
|
||||
GLES20.glAttachShader(mProgram, vertexShader) // add the vertex
|
||||
// shader to program
|
||||
GLES20.glAttachShader(mProgram, fragmentShader) // add the fragment
|
||||
// shader to
|
||||
// program
|
||||
GLES20.glLinkProgram(mProgram)
|
||||
val positionHandle = GLES20.glGetAttribLocation(mProgram,
|
||||
"aPosition")
|
||||
val textureHandle = GLES20.glGetAttribLocation(mProgram,
|
||||
"aTextureCoord")
|
||||
GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
|
||||
GLES20.GL_FLOAT, false, COORDS_PER_VERTEX * 4,
|
||||
mVertexBuffer)
|
||||
GLES20.glEnableVertexAttribArray(positionHandle)
|
||||
GLES20.glVertexAttribPointer(textureHandle,
|
||||
TEXTURECOORDS_PER_VERTEX, GLES20.GL_FLOAT, false,
|
||||
TEXTURECOORDS_PER_VERTEX * 4, mTextureBuffer)
|
||||
GLES20.glEnableVertexAttribArray(textureHandle)
|
||||
GLES20.glUseProgram(mProgram)
|
||||
var i = GLES20.glGetUniformLocation(mProgram, "Ytex")
|
||||
GLES20.glUniform1i(i, 0) /* Bind Ytex to texture unit 0 */
|
||||
i = GLES20.glGetUniformLocation(mProgram, "Utex")
|
||||
GLES20.glUniform1i(i, 1) /* Bind Utex to texture unit 1 */
|
||||
i = GLES20.glGetUniformLocation(mProgram, "Vtex")
|
||||
GLES20.glUniform1i(i, 2) /* Bind Vtex to texture unit 2 */
|
||||
val radiusDpLocation = GLES20.glGetUniformLocation(mProgram, "radiusDp")
|
||||
val radiusDp = (Resources.getSystem().displayMetrics.density * THUMBNAIL_SIZE).toInt()
|
||||
GLES20.glUniform2f(radiusDpLocation, radiusDp.toFloat(), radiusDp.toFloat())
|
||||
mTextureWidth = 0
|
||||
mTextureHeight = 0
|
||||
}
|
||||
|
||||
fun setupTextures(frame: Frame) {
|
||||
if (mTextureIds[0] != 0) {
|
||||
GLES20.glDeleteTextures(3, mTextureIds, 0)
|
||||
}
|
||||
GLES20.glGenTextures(3, mTextureIds, 0)
|
||||
val w = frame.width
|
||||
val h = frame.height
|
||||
val hw = w + 1 shr 1
|
||||
val hh = h + 1 shr 1
|
||||
initializeTexture(GLES20.GL_TEXTURE0, mTextureIds[0], w, h)
|
||||
initializeTexture(GLES20.GL_TEXTURE1, mTextureIds[1], hw, hh)
|
||||
initializeTexture(GLES20.GL_TEXTURE2, mTextureIds[2], hw, hh)
|
||||
mTextureWidth = frame.width
|
||||
mTextureHeight = frame.height
|
||||
}
|
||||
|
||||
fun updateTextures(frame: Frame) {
|
||||
val width = frame.width
|
||||
val height = frame.height
|
||||
val half_width = width + 1 shr 1
|
||||
val half_height = height + 1 shr 1
|
||||
val y_size = width * height
|
||||
val uv_size = half_width * half_height
|
||||
val bb = frame.buffer
|
||||
// If we are reusing this frame, make sure we reset position and
|
||||
// limit
|
||||
bb.clear()
|
||||
if (bb.remaining() == y_size + uv_size * 2) {
|
||||
bb.position(0)
|
||||
GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1)
|
||||
GLES20.glPixelStorei(GLES20.GL_PACK_ALIGNMENT, 1)
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[0])
|
||||
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, width,
|
||||
height, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE,
|
||||
bb)
|
||||
bb.position(y_size)
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE1)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[1])
|
||||
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0,
|
||||
half_width, half_height, GLES20.GL_LUMINANCE,
|
||||
GLES20.GL_UNSIGNED_BYTE, bb)
|
||||
bb.position(y_size + uv_size)
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE2)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIds[2])
|
||||
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0,
|
||||
half_width, half_height, GLES20.GL_LUMINANCE,
|
||||
GLES20.GL_UNSIGNED_BYTE, bb)
|
||||
} else {
|
||||
mTextureWidth = 0
|
||||
mTextureHeight = 0
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) {
|
||||
GLES20.glViewport(0, 0, width, height)
|
||||
mViewportWidth = width
|
||||
mViewportHeight = height
|
||||
}
|
||||
|
||||
var metadataListener: ThumbnailCircleVideoRendererMetadataListener? = null
|
||||
override fun onDrawFrame(gl: GL10) {
|
||||
gl.glClearColor(0f, 0f, 0f, 0f)
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
||||
mFrameLock.lock()
|
||||
if (mCurrentFrame != null && !mVideoDisabled) {
|
||||
GLES20.glUseProgram(mProgram)
|
||||
if (mTextureWidth != mCurrentFrame!!.width
|
||||
|| mTextureHeight != mCurrentFrame!!.height) {
|
||||
setupTextures(mCurrentFrame!!)
|
||||
}
|
||||
updateTextures(mCurrentFrame!!)
|
||||
Matrix.setIdentityM(mScaleMatrix, 0)
|
||||
var scaleX = 1.0f
|
||||
var scaleY = 1.0f
|
||||
val ratio = (mCurrentFrame!!.width.toFloat()
|
||||
/ mCurrentFrame!!.height)
|
||||
val vratio = mViewportWidth.toFloat() / mViewportHeight
|
||||
if (mVideoFitEnabled) {
|
||||
if (ratio > vratio) {
|
||||
scaleY = vratio / ratio
|
||||
} else {
|
||||
scaleX = ratio / vratio
|
||||
}
|
||||
} else {
|
||||
if (ratio < vratio) {
|
||||
scaleY = vratio / ratio
|
||||
} else {
|
||||
scaleX = ratio / vratio
|
||||
}
|
||||
}
|
||||
Matrix.scaleM(mScaleMatrix, 0,
|
||||
scaleX * if (mCurrentFrame!!.isMirroredX) -1.0f else 1.0f,
|
||||
scaleY, 1f)
|
||||
metadataListener?.onMetadataReady(mCurrentFrame!!.metadata)
|
||||
val mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram,
|
||||
"uMVPMatrix")
|
||||
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false,
|
||||
mScaleMatrix, 0)
|
||||
GLES20.glDrawElements(GLES20.GL_TRIANGLES, mVertexIndex.size,
|
||||
GLES20.GL_UNSIGNED_SHORT, mDrawListBuffer)
|
||||
} else {
|
||||
//black frame when video is disabled
|
||||
gl.glClearColor(0f, 0f, 0f, 1f)
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
||||
}
|
||||
mFrameLock.unlock()
|
||||
}
|
||||
|
||||
fun displayFrame(frame: Frame?) {
|
||||
mFrameLock.lock()
|
||||
if (mCurrentFrame != null) {
|
||||
mCurrentFrame!!.recycle()
|
||||
}
|
||||
mCurrentFrame = frame
|
||||
mFrameLock.unlock()
|
||||
}
|
||||
|
||||
fun disableVideo(b: Boolean) {
|
||||
mFrameLock.lock()
|
||||
mVideoDisabled = b
|
||||
if (mVideoDisabled) {
|
||||
if (mCurrentFrame != null) {
|
||||
mCurrentFrame!!.recycle()
|
||||
}
|
||||
mCurrentFrame = null
|
||||
}
|
||||
mFrameLock.unlock()
|
||||
}
|
||||
|
||||
fun enableVideoFit(enableVideoFit: Boolean) {
|
||||
mVideoFitEnabled = enableVideoFit
|
||||
}
|
||||
|
||||
companion object {
|
||||
// number of coordinates per vertex in this array
|
||||
const val COORDS_PER_VERTEX = 3
|
||||
const val TEXTURECOORDS_PER_VERTEX = 2
|
||||
var mXYZCoords = floatArrayOf(
|
||||
-1.0f, 1.0f, 0.0f, // top left
|
||||
-1.0f, -1.0f, 0.0f, // bottom left
|
||||
1.0f, -1.0f, 0.0f, // bottom right
|
||||
1.0f, 1.0f, 0.0f // top right
|
||||
)
|
||||
var mUVCoords = floatArrayOf(0f, 0f, 0f, 1f, 1f, 1f, 1f, 0f)
|
||||
fun initializeTexture(name: Int, id: Int, width: Int, height: Int) {
|
||||
GLES20.glActiveTexture(name)
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, id)
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST.toFloat())
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR.toFloat())
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE.toFloat())
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE.toFloat())
|
||||
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE,
|
||||
width, height, 0, GLES20.GL_LUMINANCE,
|
||||
GLES20.GL_UNSIGNED_BYTE, null)
|
||||
}
|
||||
|
||||
fun loadShader(type: Int, shaderCode: String?): Int {
|
||||
val shader = GLES20.glCreateShader(type)
|
||||
GLES20.glShaderSource(shader, shaderCode)
|
||||
GLES20.glCompileShader(shader)
|
||||
return shader
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
val bb = ByteBuffer.allocateDirect(mXYZCoords.size * 4)
|
||||
bb.order(ByteOrder.nativeOrder())
|
||||
mVertexBuffer = bb.asFloatBuffer()
|
||||
mVertexBuffer.put(mXYZCoords)
|
||||
mVertexBuffer.position(0)
|
||||
val tb = ByteBuffer.allocateDirect(mUVCoords.size * 4)
|
||||
tb.order(ByteOrder.nativeOrder())
|
||||
mTextureBuffer = tb.asFloatBuffer()
|
||||
mTextureBuffer.put(mUVCoords)
|
||||
mTextureBuffer.position(0)
|
||||
val dlb = ByteBuffer.allocateDirect(mVertexIndex.size * 2)
|
||||
dlb.order(ByteOrder.nativeOrder())
|
||||
mDrawListBuffer = dlb.asShortBuffer()
|
||||
mDrawListBuffer.put(mVertexIndex)
|
||||
mDrawListBuffer.position(0)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFrame(frame: Frame) {
|
||||
mRenderer.displayFrame(frame)
|
||||
mView.requestRender()
|
||||
}
|
||||
|
||||
override fun setStyle(key: String, value: String) {
|
||||
if (STYLE_VIDEO_SCALE == key) {
|
||||
if (STYLE_VIDEO_FIT == value) {
|
||||
mRenderer.enableVideoFit(true)
|
||||
} else if (STYLE_VIDEO_FILL == value) {
|
||||
mRenderer.enableVideoFit(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onVideoPropertiesChanged(videoEnabled: Boolean) {
|
||||
mRenderer.disableVideo(!videoEnabled)
|
||||
}
|
||||
|
||||
override fun getView(): View {
|
||||
return mView
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
mView.onPause()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
mView.onResume()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val THUMBNAIL_SIZE = 90 //in dp
|
||||
}
|
||||
|
||||
init {
|
||||
mView.setEGLContextClientVersion(2)
|
||||
mView.setEGLConfigChooser(8, 8, 8, 8, 16, 0)
|
||||
mView.holder.setFormat(PixelFormat.TRANSLUCENT)
|
||||
mRenderer = MyRenderer()
|
||||
mView.setRenderer(mRenderer)
|
||||
mView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY
|
||||
}
|
||||
}
|
||||
@ -1,58 +0,0 @@
|
||||
package com.hmg.hmgDr.util
|
||||
|
||||
import android.content.Context
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||
import kotlin.math.ceil
|
||||
|
||||
object ViewsUtil {
|
||||
|
||||
/* return status bar height on basis of device display metrics */
|
||||
fun getStatusBarHeight(context: Context): Int {
|
||||
return ceil(
|
||||
(25 * context.resources.displayMetrics.density).toDouble()
|
||||
).toInt()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context
|
||||
* @return the Screen height in DP
|
||||
*/
|
||||
fun getHeightDp(context: Context, isInDp: Boolean = false): Float {
|
||||
val displayMetrics: DisplayMetrics = context.resources.displayMetrics
|
||||
return if (isInDp) {
|
||||
displayMetrics.heightPixels / displayMetrics.density
|
||||
} else {
|
||||
displayMetrics.heightPixels.toFloat()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context
|
||||
* @return the screnn width in dp
|
||||
*/
|
||||
fun getWidthDp(context: Context, isInDp: Boolean = false): Float {
|
||||
val displayMetrics: DisplayMetrics = context.resources.displayMetrics
|
||||
return if (isInDp) {
|
||||
displayMetrics.widthPixels / displayMetrics.density
|
||||
} else {
|
||||
displayMetrics.widthPixels.toFloat()
|
||||
}
|
||||
}
|
||||
|
||||
// code to hide soft keyboard
|
||||
fun hideSoftKeyBoard(context: Context, editBox: EditText?) {
|
||||
val imm = context.getSystemService(FlutterFragmentActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.hideSoftInputFromWindow(editBox?.windowToken, 0)
|
||||
}
|
||||
|
||||
|
||||
// code to show soft keyboard
|
||||
private fun showSoftKeyBoard(context: Context, editBox: EditText?) {
|
||||
val inputMethodManager = context.getSystemService(FlutterFragmentActivity.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
editBox?.requestFocus()
|
||||
inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0)
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
|
||||
<solid
|
||||
android:color="@color/remoteBackground"/>
|
||||
|
||||
<stroke android:width="2dp" android:color="@color/text_color" />
|
||||
|
||||
<size
|
||||
android:width="120dp"
|
||||
android:height="120dp"/>
|
||||
</shape>
|
||||
|
Before Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
@ -1,5 +0,0 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M7.41,8.59L12,13.17l4.59,-4.58L18,10l-6,6 -6,-6 1.41,-1.41z"/>
|
||||
</vector>
|
||||
@ -1,5 +0,0 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"/>
|
||||
</vector>
|
||||
@ -1,5 +0,0 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,9c-1.6,0 -3.15,0.25 -4.6,0.72v3.1c0,0.39 -0.23,0.74 -0.56,0.9 -0.98,0.49 -1.87,1.12 -2.66,1.85 -0.18,0.18 -0.43,0.28 -0.7,0.28 -0.28,0 -0.53,-0.11 -0.71,-0.29L0.29,13.08c-0.18,-0.17 -0.29,-0.42 -0.29,-0.7 0,-0.28 0.11,-0.53 0.29,-0.71C3.34,8.78 7.46,7 12,7s8.66,1.78 11.71,4.67c0.18,0.18 0.29,0.43 0.29,0.71 0,0.28 -0.11,0.53 -0.29,0.71l-2.48,2.48c-0.18,0.18 -0.43,0.29 -0.71,0.29 -0.27,0 -0.52,-0.11 -0.7,-0.28 -0.79,-0.74 -1.69,-1.36 -2.67,-1.85 -0.33,-0.16 -0.56,-0.5 -0.56,-0.9v-3.1C15.15,9.25 13.6,9 12,9z"/>
|
||||
</vector>
|
||||
@ -1,5 +0,0 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M19,13H5v-2h14v2z"/>
|
||||
</vector>
|
||||
|
Before Width: | Height: | Size: 16 KiB |
@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/text_color" />
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="@color/text_color" />
|
||||
<corners android:radius="10dp" />
|
||||
<padding
|
||||
android:bottom="0dp"
|
||||
android:left="0dp"
|
||||
android:right="0dp"
|
||||
android:top="0dp" />
|
||||
</shape>
|
||||
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 4.8 KiB |
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="1000dp"/>
|
||||
|
||||
<solid android:color="@color/green_dark"/>
|
||||
|
||||
</shape>
|
||||
|
Before Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.2 KiB |
@ -1,215 +1,169 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/video_call_ll"
|
||||
android:id="@+id/activity_clingo_video_call"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/text_color"
|
||||
android:orientation="vertical">
|
||||
|
||||
tools:context=".ui.VideoCallActivity">
|
||||
<RelativeLayout
|
||||
android:id="@+id/layout_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/layout_name_height"
|
||||
android:padding="@dimen/padding_space_medium"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/patient_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toStartOf="@+id/video_counter_fl"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_size_big"
|
||||
android:textStyle="bold"
|
||||
tools:text="Mousa Abuzaid" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/video_counter_fl"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:gravity="center_horizontal"
|
||||
android:keepScreenOn="true"
|
||||
android:clickable="true">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/subscriberview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:background="@drawable/shape_capsule"
|
||||
android:padding="@dimen/padding_space_small">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_timer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="4dp"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:text="00:00" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:orientation="horizontal"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/layout_mini"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/layout_mini_height"
|
||||
android:background="@color/remoteBackground"
|
||||
app:layout_constraintTop_toBottomOf="@+id/layout_name"
|
||||
android:alpha="0.5"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/ic_mini"
|
||||
style="@style/Widget.MaterialComponents.Button.Icon"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_margin="@dimen/padding_space_medium"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="@null"
|
||||
android:src="@drawable/ic_mini" />
|
||||
<RelativeLayout
|
||||
android:id="@+id/publisherview"
|
||||
android:layout_height="200dp"
|
||||
android:layout_width="150dp"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin" />
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/activity_clingo_video_call"
|
||||
android:id="@+id/remote_video_view_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/control_panel"
|
||||
app:layout_constraintTop_toBottomOf="@+id/layout_mini">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/remote_video_view_container"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/remoteBackground">
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/remoteBackground">
|
||||
android:layout_above="@id/icon_padding">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/remote_video_view_icon"
|
||||
android:layout_width="@dimen/remote_back_icon_size"
|
||||
android:layout_height="@dimen/remote_back_icon_size"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/video_off_fill" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/local_video_view_container"
|
||||
android:layout_width="@dimen/local_preview_width"
|
||||
android:layout_height="@dimen/local_preview_height"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginTop="@dimen/local_preview_margin_top"
|
||||
android:layout_marginEnd="@dimen/local_preview_margin_top">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/local_video_view_icon"
|
||||
android:layout_width="@dimen/local_back_icon_size"
|
||||
android:layout_height="@dimen/local_back_icon_size"
|
||||
android:layout_gravity="center"
|
||||
android:scaleType="centerCrop"
|
||||
android:layout_centerInParent="true"
|
||||
android:src="@drawable/video_off_fill" />
|
||||
</FrameLayout>
|
||||
</RelativeLayout>
|
||||
<RelativeLayout
|
||||
android:id="@+id/icon_padding"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/remote_back_icon_margin_bottom"
|
||||
android:layout_alignParentBottom="true"/>
|
||||
</RelativeLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/record_container"
|
||||
android:layout_width="@dimen/local_preview_width"
|
||||
android:layout_height="@dimen/local_preview_height"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:visibility="visible">
|
||||
<FrameLayout
|
||||
android:id="@+id/local_video_view_container"
|
||||
android:layout_width="@dimen/local_preview_width"
|
||||
android:layout_height="@dimen/local_preview_height"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginEnd="@dimen/local_preview_margin_right"
|
||||
android:layout_marginRight="@dimen/local_preview_margin_right"
|
||||
android:layout_marginTop="@dimen/local_preview_margin_top"
|
||||
android:background="@color/localBackground">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/record_icon"
|
||||
android:layout_width="@dimen/local_back_icon_size"
|
||||
android:layout_height="@dimen/local_back_icon_size"
|
||||
android:scaleType="centerCrop"
|
||||
android:layout_margin="5dp"
|
||||
android:src="@drawable/ic_record" />
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/thumbnail_container"
|
||||
android:layout_width="90dp"
|
||||
android:layout_height="90dp"
|
||||
android:visibility="gone"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
/>
|
||||
|
||||
</RelativeLayout>
|
||||
<ImageView
|
||||
android:layout_width="@dimen/local_back_icon_size"
|
||||
android:layout_height="@dimen/local_back_icon_size"
|
||||
android:layout_gravity="center"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/video_off_fill" />
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
<RelativeLayout
|
||||
android:id="@+id/control_panel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/layout_panel_height"
|
||||
android:padding="@dimen/padding_space_big"
|
||||
app:layout_constraintBottom_toBottomOf="parent">
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginBottom="60dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btn_call"
|
||||
android:layout_width="@dimen/video_icon_size"
|
||||
android:layout_height="@dimen/video_icon_size"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/call"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
android:layout_width="71dp"
|
||||
android:layout_height="71dp"
|
||||
android:layout_centerInParent="true"
|
||||
android:onClick="onCallClicked"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/call" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btn_minimize"
|
||||
android:layout_width="@dimen/video_icon_size"
|
||||
android:layout_height="@dimen/video_icon_size"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/reducing"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
android:id="@+id/btn_switch_camera"
|
||||
android:layout_width="39dp"
|
||||
android:layout_height="39dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginLeft="@dimen/control_bottom_horizontal_margin"
|
||||
android:layout_toEndOf="@id/btn_camera"
|
||||
android:layout_toRightOf="@id/btn_camera"
|
||||
android:onClick="onSwitchCameraClicked"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/flip_enabled" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btn_camera"
|
||||
android:layout_width="@dimen/video_icon_size"
|
||||
android:layout_height="@dimen/video_icon_size"
|
||||
android:layout_marginStart="@dimen/padding_space_medium"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/video_enabled"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/btn_minimize"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
android:layout_width="39dp"
|
||||
android:layout_height="39dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginLeft="@dimen/control_bottom_horizontal_margin"
|
||||
android:layout_toEndOf="@id/btn_call"
|
||||
android:layout_toRightOf="@id/btn_call"
|
||||
android:onClick="onCameraClicked"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/video_enabled" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btn_mic"
|
||||
android:layout_width="@dimen/video_icon_size"
|
||||
android:layout_height="@dimen/video_icon_size"
|
||||
android:layout_marginStart="@dimen/padding_space_medium"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/mic_enabled"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/btn_camera"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btn_switch_camera"
|
||||
android:layout_width="@dimen/video_icon_size"
|
||||
android:layout_height="@dimen/video_icon_size"
|
||||
android:layout_marginStart="@dimen/padding_space_medium"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/camera_back"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/btn_mic"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
android:layout_width="39dp"
|
||||
android:layout_height="39dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginRight="@dimen/control_bottom_horizontal_margin"
|
||||
android:layout_toStartOf="@id/btn_call"
|
||||
android:layout_toLeftOf="@id/btn_call"
|
||||
android:onClick="onMicClicked"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/mic_enabled" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btn_specker"
|
||||
android:layout_width="@dimen/video_icon_size"
|
||||
android:layout_height="@dimen/video_icon_size"
|
||||
android:layout_marginStart="@dimen/padding_space_medium"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/audio_enabled"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/btn_mic"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
android:layout_width="39dp"
|
||||
android:layout_height="39dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginRight="@dimen/control_bottom_horizontal_margin"
|
||||
android:layout_toStartOf="@id/btn_mic"
|
||||
android:layout_toLeftOf="@id/btn_mic"
|
||||
android:onClick="onSpeckerClicked"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/audio_enabled" />
|
||||
</RelativeLayout>
|
||||
|
||||
<!-- <RelativeLayout-->
|
||||
<!-- android:id="@+id/progressBar"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="40dp"-->
|
||||
<!-- android:layout_alignParentBottom="true">-->
|
||||
|
||||
<!-- <ProgressBar-->
|
||||
<!-- android:id="@+id/progress_bar"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="31dp"-->
|
||||
<!-- android:layout_alignParentEnd="true"-->
|
||||
<!-- android:layout_alignParentBottom="true"-->
|
||||
<!-- android:layout_marginEnd="0dp"-->
|
||||
<!-- android:layout_marginBottom="0dp"-->
|
||||
<!-- android:progressBackgroundTint="@color/colorProgressBarBackground"-->
|
||||
<!-- style="@android:style/Widget.ProgressBar.Horizontal" />-->
|
||||
|
||||
<!-- <TextView-->
|
||||
<!-- android:id="@+id/progress_bar_text"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:layout_marginLeft="9dp"-->
|
||||
<!-- android:gravity="center_vertical"-->
|
||||
<!-- android:textColor="@color/colorPrimary"-->
|
||||
<!-- android:layout_centerInParent="true"/>-->
|
||||
|
||||
<!-- </RelativeLayout>-->
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
@ -1,81 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:fadeScrollbars="false"
|
||||
android:scrollbars="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/ask_for_error_log"
|
||||
android:textColor="#212121"
|
||||
android:textSize="18sp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_view_error_log"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="View Error Log"
|
||||
android:textColor="#212121"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_copy_error_log"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="Copy Error Log"
|
||||
android:textColor="#212121"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_share_error_log"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="Share Error Log"
|
||||
android:textColor="#212121"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_email_error_log"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="Email Error Log"
|
||||
android:textColor="#212121"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_save_error_log"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="Save Error Log"
|
||||
android:textColor="#212121"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_close_app"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Close App"
|
||||
android:textColor="#212121"/>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
@ -1,79 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="88dp"
|
||||
android:background="@android:color/holo_blue_dark"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/padding_space_medium">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_icon"
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:src="@mipmap/ic_launcher" />
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.Compat.Notification"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/padding_space_small"
|
||||
android:paddingEnd="@dimen/padding_space_small"
|
||||
android:text="HMG Doctor"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_size_small" />
|
||||
|
||||
<Chronometer
|
||||
android:id="@+id/notify_timer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
style="@style/TextAppearance.Compat.Notification"
|
||||
android:paddingStart="@dimen/padding_space_small"
|
||||
android:paddingEnd="@dimen/padding_space_small"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
android:format="MM:SS"
|
||||
tools:text="25:45" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_Arrow"
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:src="@drawable/ic_arrow_bottom" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notify_title"
|
||||
style="@style/TextAppearance.Compat.Notification.Title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/padding_space_medium"
|
||||
android:paddingStart="@dimen/padding_space_small"
|
||||
android:paddingEnd="@dimen/padding_space_small"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
android:textStyle="bold"
|
||||
tools:text="Mosa zaid mosa abuzaid" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notify_content"
|
||||
style="@style/TextAppearance.Compat.Notification.Title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/padding_space_small"
|
||||
android:paddingEnd="@dimen/padding_space_small"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
android:text="Tap to return to call" />
|
||||
|
||||
</LinearLayout>
|
||||
@ -1,94 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/holo_blue_dark"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/padding_space_medium">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_icon"
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:src="@mipmap/ic_launcher" />
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.Compat.Notification"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/padding_space_small"
|
||||
android:paddingEnd="@dimen/padding_space_small"
|
||||
android:text="HMG Doctor"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_size_small" />
|
||||
|
||||
<Chronometer
|
||||
android:id="@+id/notify_timer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
style="@style/TextAppearance.Compat.Notification"
|
||||
android:paddingStart="@dimen/padding_space_small"
|
||||
android:paddingEnd="@dimen/padding_space_small"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
android:format="MM:SS"
|
||||
tools:text="25:45" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_Arrow"
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:src="@drawable/ic_arrow_top" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notify_title"
|
||||
style="@style/TextAppearance.Compat.Notification.Title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/padding_space_big"
|
||||
android:paddingStart="@dimen/padding_space_small"
|
||||
android:paddingEnd="@dimen/padding_space_small"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
tools:text="Mosa zaid mosa abuzaid" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notify_content"
|
||||
style="@style/TextAppearance.Compat.Notification.Title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/padding_space_small"
|
||||
android:paddingEnd="@dimen/padding_space_small"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:text="Tap to return to call" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btn_end"
|
||||
style="@style/TextAppearance.Compat.Notification.Title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/padding_space_medium"
|
||||
android:layout_marginBottom="@dimen/padding_space_medium"
|
||||
android:paddingStart="@dimen/padding_space_small"
|
||||
android:paddingEnd="@dimen/padding_space_small"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
android:text="End call" />
|
||||
|
||||
</LinearLayout>
|
||||
@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<external-path name="external_files" path="."/>
|
||||
</paths>
|
||||
@ -1 +0,0 @@
|
||||
include ':app'
|
||||
|
After Width: | Height: | Size: 9.5 KiB |
|
After Width: | Height: | Size: 348 KiB |
|
After Width: | Height: | Size: 247 B |
|
After Width: | Height: | Size: 412 B |
|
After Width: | Height: | Size: 946 B |
|
After Width: | Height: | Size: 651 B |
|
After Width: | Height: | Size: 655 KiB |
|
After Width: | Height: | Size: 577 KiB |
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.1.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 73 74" style="enable-background:new 0 0 73 74;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#E3E3E3;}
|
||||
.st1{fill:none;stroke:#CCCCCC;}
|
||||
.st2{fill:#B4B4B4;}
|
||||
</style>
|
||||
<g id="Ellipse_123-2" transform="translate(2933 475)">
|
||||
<circle class="st0" cx="-2896.5" cy="-437.5" r="35"/>
|
||||
<circle class="st1" cx="-2896.5" cy="-437.5" r="34.5"/>
|
||||
</g>
|
||||
<path class="st2" d="M40.25,43.44c-0.02-0.24-0.04-0.63-0.05-1.04c3.94-0.38,6.7-1.26,6.7-2.29c-0.01,0-0.01-0.04-0.01-0.06
|
||||
c-2.94-2.48,2.55-20.09-7.68-19.73c-0.64-0.51-1.77-0.96-3.38-0.96c-13.86,0.98-7.73,17.61-10.89,20.75c0,0,0,0-0.01,0
|
||||
c0,0,0,0,0,0.01c0,0,0,0,0,0s0,0,0,0c0.01,1.01,2.68,1.87,6.51,2.26c-0.01,0.25-0.03,0.55-0.08,1.06
|
||||
c-1.59,3.99-12.3,2.87-12.8,10.56H53C52.51,46.31,41.84,47.43,40.25,43.44z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,7 @@
|
||||
<svg id="enter" xmlns="http://www.w3.org/2000/svg" width="42" height="42" viewBox="0 0 42 42">
|
||||
<path id="Path_1146" data-name="Path 1146" d="M17.938,210.625H1.313a1.313,1.313,0,1,1,0-2.625H17.938a1.313,1.313,0,1,1,0,2.625Zm0,0" transform="translate(22.749 -190.938)"/>
|
||||
<path id="Path_1147" data-name="Path 1147" d="M123.984,143.736a1.313,1.313,0,0,1-.928-2.242l5.635-5.635-5.635-5.633a1.313,1.313,0,0,1,1.857-1.857l6.563,6.562a1.313,1.313,0,0,1,0,1.857l-6.562,6.562A1.31,1.31,0,0,1,123.984,143.736Zm0,0" transform="translate(-89.86 -117.486)"/>
|
||||
<path id="Path_1148" data-name="Path 1148" d="M312.668,42.076a3.5,3.5,0,0,0,3.5-3.5V7.076A3.522,3.522,0,0,0,313.8,3.753L303.284.248a3.537,3.537,0,0,0-4.616,3.329v31.5a3.524,3.524,0,0,0,2.368,3.321L311.553,41.9A3.662,3.662,0,0,0,312.668,42.076ZM302.168,2.7a1.03,1.03,0,0,1,.313.046l10.47,3.491a.9.9,0,0,1,.592.838v31.5a.922.922,0,0,1-1.188.83l-10.47-3.491a.905.905,0,0,1-.591-.838V3.576A.876.876,0,0,1,302.168,2.7Zm0,0" transform="translate(-298.668 -0.076)"/>
|
||||
<path id="Path_1149" data-name="Path 1149" d="M195.168,8.75a1.313,1.313,0,0,0,1.313-1.312V4.813A4.816,4.816,0,0,0,191.668,0H171.98a1.313,1.313,0,0,0,0,2.625h19.688a2.19,2.19,0,0,1,2.187,2.188V7.438A1.313,1.313,0,0,0,195.168,8.75Zm0,0" transform="translate(-168.48)"/>
|
||||
<path id="Path_1150" data-name="Path 1150" d="M171.98,350.082h7a4.816,4.816,0,0,0,4.812-4.813v-2.625a1.313,1.313,0,0,0-2.625,0v2.625a2.19,2.19,0,0,1-2.187,2.188h-7a1.313,1.313,0,0,0,0,2.625Zm0,0" transform="translate(-155.793 -313.332)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 753 B |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 246 B |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 206 B |
|
After Width: | Height: | Size: 987 B |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 409 B |
|
After Width: | Height: | Size: 942 B |
|
After Width: | Height: | Size: 765 B |
|
After Width: | Height: | Size: 232 B |