package org.linphone.assistant; /* RemoteProvisioningLoginActivity.java Copyright (C) 2017 Belledonne Communications, Grenoble, France This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import org.linphone.LinphoneManager; import org.linphone.LinphonePreferences; import org.linphone.R; import org.linphone.activities.LinphoneActivity; import org.linphone.core.ConfiguringState; import org.linphone.core.Core; import org.linphone.core.CoreListenerStub; import org.linphone.core.ProxyConfig; import org.linphone.core.RegistrationState; import org.linphone.mediastream.Log; import org.linphone.mediastream.video.AndroidVideoWindowImpl; import org.linphone.mediastream.video.capture.hwconf.AndroidCameraConfiguration; import android.Manifest; import android.app.Activity; import android.app.ProgressDialog; import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.util.Base64; import android.view.SurfaceView; import android.view.View; import android.view.View.OnClickListener; import android.view.WindowManager; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import java.io.ByteArrayInputStream; import java.math.BigInteger; import java.security.spec.KeySpec; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import static android.os.SystemClock.sleep; public class RemoteProvisioningLoginActivity extends Activity implements OnClickListener { private static RemoteProvisioningLoginActivity instance; private EditText code_sms; private TextView step, instruction; private Button ok, back; private ProgressDialog progress; private String qrcodeString; private String remoteUrl; private LinearLayout bottom; private RelativeLayout layout_button; private CoreListenerStub mListener; private SurfaceView mQrcodeView; private ImageView mImageMask; private AndroidVideoWindowImpl androidVideoWindowImpl; private boolean cameraAuthorize = false; private boolean readQRCode = true; private boolean backCamera = true; private int PERMISSION_CAMERA = 108; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); instance = this; setContentView(R.layout.assistant_remote_provisioning_login); if (getPackageManager().checkPermission(Manifest.permission.CAMERA, getPackageName()) != PackageManager.PERMISSION_GRANTED) { checkAndRequestVideoPermission(); } else { cameraAuthorize = true; } mQrcodeView = (SurfaceView) findViewById(R.id.qrcodeCaptureSurface); bottom = (LinearLayout) findViewById(R.id.bottom_text); code_sms = (EditText) findViewById(R.id.code_sms); step = (TextView) findViewById(R.id.step); instruction = (TextView) findViewById(R.id.instruction); mImageMask = (ImageView) findViewById(R.id.imageView4); layout_button = (RelativeLayout) findViewById(R.id.layout_button); ok = (Button) findViewById(R.id.valider); back = (Button) findViewById(R.id.retour); ok.setOnClickListener(this); back.setOnClickListener(this); mImageMask.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { if (readQRCode && getPackageManager().checkPermission(Manifest.permission.CAMERA, getPackageName()) != PackageManager.PERMISSION_GRANTED) { checkAndRequestVideoPermission(); } else { instance.setBackCamera(!backCamera); } } }); mListener = new CoreListenerStub() { @Override public void onQrcodeFound(Core lc, String result) { instance.qrcodeString = result; runOnUiThread(new Runnable() { @Override public void run() { instance.enableQrcodeReader(false); instance.displayCodeSms(); } }); } @Override public void onConfiguringStatus(Core lc, final ConfiguringState state, String message) { if (state == ConfiguringState.Successful) { //TODO } else if (state == ConfiguringState.Failed) { Toast.makeText(RemoteProvisioningLoginActivity.this, R.string.remote_provisioning_failure, Toast.LENGTH_LONG).show(); runOnUiThread(new Runnable() { @Override public void run() { instance.ok.setEnabled(true); } }); if (progress != null) progress.dismiss(); runOnUiThread(new Runnable() { @Override public void run() { instance.displayQrCode(); instance.launchQrcodeReader(); } }); } } @Override public void onRegistrationStateChanged(Core lc, ProxyConfig proxy, RegistrationState state, String smessage) { if (state.equals(RegistrationState.Ok)) { LinphonePreferences.instance().firstLaunchSuccessful(); startActivity(new Intent().setClass(RemoteProvisioningLoginActivity.this, LinphoneActivity.class).setData(getIntent().getData())); finish(); } if (progress != null) progress.dismiss(); } }; displayQrCode(); } void checkAndRequestVideoPermission() { int permissionGranted = getPackageManager().checkPermission(Manifest.permission.CAMERA, getPackageName()); Log.i("[Permission] " + Manifest.permission.CAMERA + " is " + (permissionGranted == PackageManager.PERMISSION_GRANTED ? "granted" : "denied")); if (permissionGranted != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, PERMISSION_CAMERA); } } private void cancelWizard(boolean bypassCheck) { if (bypassCheck || getResources().getBoolean(R.bool.allow_cancel_remote_provisioning_login_activity)) { LinphonePreferences.instance().disableProvisioningLoginView(); setResult(bypassCheck ? Activity.RESULT_OK : Activity.RESULT_CANCELED); finish(); } } private void displayQrCode() { mQrcodeView.setVisibility(View.VISIBLE); mImageMask.setVisibility(View.VISIBLE); code_sms.setVisibility(View.GONE); ok.setVisibility(View.GONE); back.setVisibility(View.GONE); layout_button.setVisibility(View.GONE); step.setText(getString(R.string.assistant_step1)); instruction.setText(getString(R.string.assistant_instruction1)); readQRCode = true; } private void displayCodeSms() { mQrcodeView.setVisibility(View.GONE); mImageMask.setVisibility(View.GONE); code_sms.setVisibility(View.VISIBLE); ok.setVisibility(View.VISIBLE); back.setVisibility(View.VISIBLE); layout_button.setVisibility(View.VISIBLE); step.setText(getString(R.string.assistant_step2)); instruction.setText(getString(R.string.assistant_instruction2)); readQRCode = false; } private void enableQrcodeReader(boolean enable) { if (cameraAuthorize && readQRCode) { LinphoneManager.getLc().enableQrcodeVideoPreview(enable); LinphoneManager.getLc().enableVideoPreview(enable); } } private void setBackCamera(boolean useBackCamera) { int camId = 0; AndroidCameraConfiguration.AndroidCamera[] cameras = AndroidCameraConfiguration.retrieveCameras(); for (AndroidCameraConfiguration.AndroidCamera androidCamera : cameras) { if (androidCamera.frontFacing == !useBackCamera) camId = androidCamera.id; } String[] devices = LinphoneManager.getLc().getVideoDevicesList(); String newDevice = devices[camId]; LinphoneManager.getLc().setVideoDevice(newDevice); backCamera = useBackCamera; } private void launchQrcodeReader() { setBackCamera(true); androidVideoWindowImpl = new AndroidVideoWindowImpl(null, mQrcodeView, new AndroidVideoWindowImpl.VideoWindowListener() { public void onVideoRenderingSurfaceReady(AndroidVideoWindowImpl vw, SurfaceView surface) {} public void onVideoRenderingSurfaceDestroyed(AndroidVideoWindowImpl vw) {} public void onVideoPreviewSurfaceReady(AndroidVideoWindowImpl vw, SurfaceView surface) { LinphoneManager.getLc().setNativePreviewWindowId(androidVideoWindowImpl); } public void onVideoPreviewSurfaceDestroyed(AndroidVideoWindowImpl vw) {} }); enableQrcodeReader(true); } @Override public void onStart() { super.onStart(); } @Override public void onResume() { super.onResume(); launchQrcodeReader(); if (androidVideoWindowImpl != null) { synchronized (androidVideoWindowImpl) { LinphoneManager.getLc().setNativePreviewWindowId(androidVideoWindowImpl); } } Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull(); if (lc != null) { lc.addListener(mListener); } } @Override public void onPause() { Core lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull(); if (lc != null) { lc.removeListener(mListener); } if (androidVideoWindowImpl != null) { synchronized (androidVideoWindowImpl) { LinphoneManager.getLc().setNativePreviewWindowId(null); } } enableQrcodeReader(false); setBackCamera(false); super.onPause(); } @Override public void onDestroy() { if (androidVideoWindowImpl != null) { androidVideoWindowImpl.release(); androidVideoWindowImpl = null; } super.onDestroy(); } @Override public void onClick(View v) { int id = v.getId(); if (id == R.id.cancel) { cancelWizard(false); } if (id == R.id.retour) { displayQrCode(); enableQrcodeReader(true); } if (id == R.id.valider) { displayRemoteProvisioningInProgressDialog(); ok.setEnabled(false); if (qrcodeString != null && (qrcodeString.startsWith("http://") || qrcodeString.startsWith("https://"))) { storeAccount(qrcodeString); } else { if (decryptQrcode()) { storeAccount(remoteUrl); } else { ok.setEnabled(true); if (progress != null) progress.cancel(); } } } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { for (int i = 0; i < permissions.length; i++) { Log.i("[Permission] " + permissions[i] + " is " + (grantResults[i] == PackageManager.PERMISSION_GRANTED ? "granted" : "denied")); } if (requestCode == PERMISSION_CAMERA) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { cameraAuthorize = true; } } } private byte[] removeUselessByte(byte[] tab, int wantedSize) { if (wantedSize == tab.length) return tab; byte[] newTab = new byte[wantedSize]; for (int i = 1 ; i < tab.length ; i++) { newTab[i-1] = tab[i]; } return newTab; } private boolean decryptQrcode() { try { byte[] unBased64Data = qrcodeString.getBytes(); ByteArrayInputStream inputStream = new ByteArrayInputStream(unBased64Data); byte[] contentToDecrypt = new byte[unBased64Data.length]; inputStream.read(contentToDecrypt); BigInteger saltHex = new BigInteger("F000000000000000", 16); BigInteger ivHex = new BigInteger("F58B8C9A49B321DBA000000000000000", 16); byte[] saltByte = removeUselessByte(saltHex.toByteArray(), 8); byte[] ivByte = removeUselessByte(ivHex.toByteArray(), 16); SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEwithSHA256AND256BITAES-CBC-BC"); KeySpec keySpec = new PBEKeySpec(code_sms.getText().toString().toCharArray(), saltByte, 10000, 128); SecretKey tmpSecretKey = factory.generateSecret(keySpec); SecretKeySpec secretKeySpec = new SecretKeySpec(tmpSecretKey.getEncoded(), "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(ivByte)); remoteUrl = new String(cipher.doFinal(Base64.decode(contentToDecrypt, Base64.DEFAULT))); } catch (Exception ex) { Toast.makeText(RemoteProvisioningLoginActivity.this, "Code mauvais", Toast.LENGTH_LONG).show(); Log.e("RemoteProvisioningLoginActivity: Decrypt problem: " + ex); remoteUrl = null; return false; } return true; } private boolean storeAccount(String url) { LinphonePreferences.instance().setRemoteProvisioningUrl(url); //TODO LinphoneManager.getLc().iterate(); sleep(1000); LinphoneManager.getLc().iterate(); //TODO LinphoneManager.getInstance().restartCore(); LinphoneManager.getLc().addListener(mListener); return true; } private void displayRemoteProvisioningInProgressDialog() { progress = ProgressDialog.show(this, null, null); Drawable d = new ColorDrawable(ContextCompat.getColor(this, R.color.colorE)); d.setAlpha(200); progress.getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); progress.getWindow().setBackgroundDrawable(d); progress.setContentView(R.layout.progress_dialog); progress.show(); } }