From dbdb72c46555dbfc14b3fe640b43161336f59e04 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Tue, 12 Jul 2016 12:26:58 +0200 Subject: [PATCH] Added video overlay when leaving Linphone while in video call --- AndroidManifest.xml | 2 + res/values/non_localizable_strings.xml | 1 + res/values/strings.xml | 2 + res/xml/preferences.xml | 15 ++- src/org/linphone/CallActivity.java | 3 +- src/org/linphone/CallVideoFragment.java | 6 ++ src/org/linphone/LinphoneActivity.java | 15 +++ src/org/linphone/LinphonePreferences.java | 10 +- src/org/linphone/LinphoneService.java | 36 ++++++- src/org/linphone/SettingsFragment.java | 18 ++++ src/org/linphone/ui/LinphoneOverlay.java | 115 ++++++++++++++++++++++ 11 files changed, 213 insertions(+), 10 deletions(-) create mode 100644 src/org/linphone/ui/LinphoneOverlay.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 7d99c6672..799dbb8fd 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -43,6 +43,8 @@ + + diff --git a/res/values/non_localizable_strings.xml b/res/values/non_localizable_strings.xml index 2d39b0234..9b3be0fc7 100644 --- a/res/values/non_localizable_strings.xml +++ b/res/values/non_localizable_strings.xml @@ -59,6 +59,7 @@ pref_wifi_only_key + pref_overlay_key pref_video_use_front_camera_key pref_video_codec_h263_key pref_video_codec_mpeg4_key diff --git a/res/values/strings.xml b/res/values/strings.xml index d949fc668..f0309c6fd 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -252,6 +252,8 @@ Video + Video overlay + Display call video in overlay when outside the application Use front camera Initiate video calls Always send video requests diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index 36abf4528..3d8656fd1 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -111,12 +111,17 @@ android:title="@string/pref_bandwidth_limit" android:key="@string/pref_bandwidth_limit_key" android:numeric="integer" /> + + - + diff --git a/src/org/linphone/CallActivity.java b/src/org/linphone/CallActivity.java index b2904a313..4b843b6df 100644 --- a/src/org/linphone/CallActivity.java +++ b/src/org/linphone/CallActivity.java @@ -30,7 +30,6 @@ import org.linphone.core.LinphoneCore; import org.linphone.core.LinphoneCoreException; import org.linphone.core.LinphoneCoreListenerBase; import org.linphone.core.LinphonePlayer; -import org.linphone.core.Reason; import org.linphone.mediastream.Log; import org.linphone.mediastream.video.capture.hwconf.AndroidCameraConfiguration; import org.linphone.ui.Numpad; @@ -38,6 +37,7 @@ import org.linphone.ui.Numpad; import android.Manifest; import android.app.Activity; import android.app.Dialog; +import android.app.Fragment; import android.app.FragmentTransaction; import android.content.Context; import android.content.Intent; @@ -56,7 +56,6 @@ import android.os.CountDownTimer; import android.os.Handler; import android.os.PowerManager; import android.os.SystemClock; -import android.app.Fragment; import android.support.v4.app.ActivityCompat; import android.support.v4.widget.DrawerLayout; import android.view.Gravity; diff --git a/src/org/linphone/CallVideoFragment.java b/src/org/linphone/CallVideoFragment.java index 7b4202fd0..31d65389e 100644 --- a/src/org/linphone/CallVideoFragment.java +++ b/src/org/linphone/CallVideoFragment.java @@ -159,6 +159,9 @@ public class CallVideoFragment extends Fragment implements OnGestureListener, On public void onResume() { super.onResume(); + if (LinphonePreferences.instance().isOverlayEnabled()) { + LinphoneService.instance().destroyOverlay(); + } if (androidVideoWindowImpl != null) { synchronized (androidVideoWindowImpl) { LinphoneManager.getLc().setVideoWindow(androidVideoWindowImpl); @@ -180,6 +183,9 @@ public class CallVideoFragment extends Fragment implements OnGestureListener, On LinphoneManager.getLc().setVideoWindow(null); } } + if (LinphonePreferences.instance().isOverlayEnabled()) { + LinphoneService.instance().createOverlay(); + } super.onPause(); } diff --git a/src/org/linphone/LinphoneActivity.java b/src/org/linphone/LinphoneActivity.java index de98f146f..560b76577 100644 --- a/src/org/linphone/LinphoneActivity.java +++ b/src/org/linphone/LinphoneActivity.java @@ -67,6 +67,7 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; +import android.provider.Settings; import android.support.v4.app.ActivityCompat; import android.support.v4.widget.DrawerLayout; import android.view.Gravity; @@ -106,6 +107,7 @@ public class LinphoneActivity extends Activity implements OnClickListener, Conta private static final int PERMISSIONS_REQUEST_RECORD_AUDIO_INCOMING_CALL = 203; private static final int PERMISSIONS_REQUEST_EXTERNAL_FILE_STORAGE = 204; private static final int PERMISSIONS_REQUEST_CAMERA = 205; + private static final int PERMISSIONS_REQUEST_OVERLAY = 206; private static LinphoneActivity instance; @@ -1108,6 +1110,10 @@ public class LinphoneActivity extends Activity implements OnClickListener, Conta } else { resetClassicMenuLayoutAndGoBackToCallIfStillRunning(); } + } else if (requestCode == PERMISSIONS_REQUEST_OVERLAY) { + if (Settings.canDrawOverlays(this)) { + LinphonePreferences.instance().enableOverlay(true); + } } else { super.onActivityResult(requestCode, resultCode, data); } @@ -1148,6 +1154,15 @@ public class LinphoneActivity extends Activity implements OnClickListener, Conta } return true; } + + public boolean checkAndRequestOverlayPermission() { + if (!Settings.canDrawOverlays(this)) { + Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())); + startActivityForResult(intent, PERMISSIONS_REQUEST_OVERLAY); + return false; + } + return true; + } public void checkAndRequestExternalStoragePermission() { if (LinphonePreferences.instance().writeExternalStoragePermAsked()) { diff --git a/src/org/linphone/LinphonePreferences.java b/src/org/linphone/LinphonePreferences.java index babfb4613..5f9a6eb5f 100644 --- a/src/org/linphone/LinphonePreferences.java +++ b/src/org/linphone/LinphonePreferences.java @@ -19,12 +19,12 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -import java.util.ArrayList; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.ArrayList; import org.linphone.core.LinphoneAddress; import org.linphone.core.LinphoneAddress.TransportType; @@ -1342,4 +1342,12 @@ public class LinphonePreferences { public void setServiceNotificationVisibility(boolean enable) { getConfig().setBool("app", "show_service_notification", enable); } + + public boolean isOverlayEnabled() { + return getConfig().getBool("app", "display_overlay", false); + } + + public void enableOverlay(boolean enable) { + getConfig().setBool("app", "display_overlay", enable); + } } diff --git a/src/org/linphone/LinphoneService.java b/src/org/linphone/LinphoneService.java index ae23baafc..2edc6636b 100644 --- a/src/org/linphone/LinphoneService.java +++ b/src/org/linphone/LinphoneService.java @@ -32,9 +32,9 @@ import org.linphone.core.LinphoneCoreException; import org.linphone.core.LinphoneCoreFactory; import org.linphone.core.LinphoneCoreListenerBase; import org.linphone.core.LinphoneProxyConfig; -import org.linphone.core.LpConfig; import org.linphone.mediastream.Log; import org.linphone.mediastream.Version; +import org.linphone.ui.LinphoneOverlay; import android.annotation.TargetApi; import android.app.Activity; @@ -49,6 +49,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.PixelFormat; import android.net.Uri; import android.os.Build; import android.os.Handler; @@ -56,6 +57,8 @@ import android.os.IBinder; import android.os.SystemClock; import android.provider.ContactsContract; import android.provider.MediaStore; +import android.view.Gravity; +import android.view.WindowManager; /** * @@ -118,6 +121,8 @@ public final class LinphoneService extends Service { private boolean mDisableRegistrationStatus; private LinphoneCoreListenerBase mListener; public static int notifcationsPriority = (Version.sdkAboveOrEqual(Version.API16_JELLY_BEAN_41) ? Notification.PRIORITY_MIN : 0); + private WindowManager mWindowManager; + private LinphoneOverlay mOverlay; public int getMessageNotifCount() { return mMsgNotifCount; @@ -207,6 +212,10 @@ public final class LinphoneService extends Service { if (state == LinphoneCall.State.IncomingReceived) { onIncomingReceived(); } + + if (state == State.CallEnd || state == State.CallReleased || state == State.Error) { + destroyOverlay(); + } if (state == State.StreamsRunning) { // Workaround bug current call seems to be updated after state changed to streams running @@ -286,8 +295,30 @@ public final class LinphoneService extends Service { , SystemClock.elapsedRealtime()+600000 , 600000 , mkeepAlivePendingIntent); - } + mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE); + } + + public void createOverlay() { + if (mOverlay != null) destroyOverlay(); + + LinphoneCall call = LinphoneManager.getLc().getCurrentCall(); + if (call == null || !call.getCurrentParamsCopy().getVideoEnabled()) return; + + mOverlay = new LinphoneOverlay(this); + WindowManager.LayoutParams params = mOverlay.getWindowManagerLayoutParams(); + params.x = 0; + params.y = 0; + mWindowManager.addView(mOverlay, params); + } + + public void destroyOverlay() { + if (mOverlay != null) { + mWindowManager.removeView(mOverlay); + mOverlay.destroy(); + } + mOverlay = null; + } private enum IncallIconState {INCALL, PAUSE, VIDEO, IDLE} private IncallIconState mCurrentIncallIconState = IncallIconState.IDLE; @@ -586,6 +617,7 @@ public final class LinphoneService extends Service { @Override public synchronized void onDestroy() { + destroyOverlay(); LinphoneCore lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull(); if (lc != null) { lc.removeListener(mListener); diff --git a/src/org/linphone/SettingsFragment.java b/src/org/linphone/SettingsFragment.java index 6f6386a59..c45d0c256 100644 --- a/src/org/linphone/SettingsFragment.java +++ b/src/org/linphone/SettingsFragment.java @@ -39,6 +39,7 @@ import org.linphone.ui.LedPreference; import org.linphone.ui.PreferencesListFragment; import android.content.Intent; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.preference.CheckBoxPreference; @@ -49,6 +50,7 @@ import android.preference.Preference.OnPreferenceChangeListener; import android.preference.Preference.OnPreferenceClickListener; import android.preference.PreferenceCategory; import android.preference.PreferenceScreen; +import android.provider.Settings; /** * @author Sylvain Berfini @@ -609,6 +611,7 @@ public class SettingsFragment extends PreferencesListFragment { ((CheckBoxPreference) findPreference(getString(R.string.pref_video_use_front_camera_key))).setChecked(mPrefs.useFrontCam()); ((CheckBoxPreference) findPreference(getString(R.string.pref_video_initiate_call_with_video_key))).setChecked(mPrefs.shouldInitiateVideoCall()); ((CheckBoxPreference) findPreference(getString(R.string.pref_video_automatically_accept_video_key))).setChecked(mPrefs.shouldAutomaticallyAcceptVideoRequests()); + ((CheckBoxPreference) findPreference(getString(R.string.pref_overlay_key))).setChecked(mPrefs.isOverlayEnabled()); } private void updateVideoPreferencesAccordingToPreset() { @@ -702,6 +705,21 @@ public class SettingsFragment extends PreferencesListFragment { return true; } }); + + findPreference(getString(R.string.pref_overlay_key)).setOnPreferenceChangeListener(new OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + boolean enable = (Boolean) newValue; + if (enable) { + if (LinphoneActivity.instance().checkAndRequestOverlayPermission()) { + mPrefs.enableOverlay(true); + } + } else { + mPrefs.enableOverlay(false); + } + return true; + } + }); } private void initCallSettings() { diff --git a/src/org/linphone/ui/LinphoneOverlay.java b/src/org/linphone/ui/LinphoneOverlay.java new file mode 100644 index 000000000..80805a95f --- /dev/null +++ b/src/org/linphone/ui/LinphoneOverlay.java @@ -0,0 +1,115 @@ +package org.linphone.ui; + +import org.linphone.LinphoneManager; +import org.linphone.core.LinphoneCall; +import org.linphone.core.LinphoneCallParams; +import org.linphone.mediastream.video.AndroidVideoWindowImpl; + +import android.content.Context; +import android.graphics.PixelFormat; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.SurfaceView; +import android.view.View; +import android.view.WindowManager; + +public class LinphoneOverlay extends org.linphone.mediastream.video.display.GL2JNIView { + private WindowManager wm; + private WindowManager.LayoutParams params; + private DisplayMetrics metrics; + private float x; + private float y; + private float touchX; + private float touchY; + private AndroidVideoWindowImpl androidVideoWindowImpl; + + public LinphoneOverlay(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs); + wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + params = new WindowManager.LayoutParams( + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.TYPE_PHONE, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT); + params.gravity = Gravity.TOP | Gravity.LEFT; + metrics = new DisplayMetrics(); + wm.getDefaultDisplay().getMetrics(metrics); + + androidVideoWindowImpl = new AndroidVideoWindowImpl(this, null, new AndroidVideoWindowImpl.VideoWindowListener() { + public void onVideoRenderingSurfaceReady(AndroidVideoWindowImpl vw, SurfaceView surface) { + LinphoneManager.getLc().setVideoWindow(vw); + } + + public void onVideoRenderingSurfaceDestroyed(AndroidVideoWindowImpl vw) { + + } + + public void onVideoPreviewSurfaceReady(AndroidVideoWindowImpl vw, SurfaceView surface) { + LinphoneManager.getLc().setPreviewWindow(null); + } + + public void onVideoPreviewSurfaceDestroyed(AndroidVideoWindowImpl vw) { + } + }); + + LinphoneCall call = LinphoneManager.getLc().getCurrentCall(); + LinphoneCallParams callParams = call.getCurrentParamsCopy(); + params.width = callParams.getReceivedVideoSize().width; + params.height = callParams.getReceivedVideoSize().height; + LinphoneManager.getLc().setVideoWindow(androidVideoWindowImpl); + + setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + + } + }); + } + + public LinphoneOverlay(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public LinphoneOverlay(Context context) { + this(context, null); + } + + public void destroy() { + androidVideoWindowImpl.release(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + x = event.getRawX(); + y = event.getRawY(); + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + touchX = event.getX(); + touchY = event.getY(); + break; + case MotionEvent.ACTION_MOVE: + updateViewPostion(); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + touchX = touchY = 0; + break; + default: + break; + } + return super.onTouchEvent(event); + } + + private void updateViewPostion() { + params.x = Math.min(Math.max(0, (int) (x - touchX)), metrics.widthPixels - getMeasuredWidth()); + params.y = Math.min(Math.max(0, (int) (y - touchY)), metrics.heightPixels - getMeasuredHeight()); + wm.updateViewLayout(this, params); + } + + public WindowManager.LayoutParams getWindowManagerLayoutParams() { + return params; + } +}