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;
+ }
+}