/* * Copyright (c) 2010-2019 Belledonne Communications SARL. * * This file is part of linphone-android * (see https://www.linphone.org). * * 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 3 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, see . */ package org.linphone.dialer; import android.Manifest; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.view.TextureView; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.asynclayoutinflater.view.AsyncLayoutInflater; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Collection; import org.linphone.BuildConfig; import org.linphone.LinphoneManager; import org.linphone.R; import org.linphone.activities.MainActivity; import org.linphone.call.views.CallButton; import org.linphone.contacts.ContactsActivity; import org.linphone.contacts.ContactsManager; import org.linphone.core.Call; import org.linphone.core.Core; import org.linphone.core.CoreListenerStub; import org.linphone.core.VersionUpdateCheckResult; import org.linphone.core.tools.Log; import org.linphone.dialer.views.AddressText; import org.linphone.dialer.views.Digit; import org.linphone.dialer.views.EraseButton; import org.linphone.settings.LinphonePreferences; import org.linphone.utils.LinphoneUtils; public class DialerActivity extends MainActivity implements AddressText.AddressChangedListener { private static final String ACTION_CALL_LINPHONE = "org.linphone.intent.action.CallLaunched"; private AddressText mAddress; private CallButton mStartCall, mAddCall, mTransferCall; private ImageView mAddContact, mBackToCall; private boolean mIsTransfer; private CoreListenerStub mListener; private boolean mInterfaceLoaded; private String mAddressToCallOnLayoutReady; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mInterfaceLoaded = false; // Uses the fragment container layout to inflate the dialer view instead of using a fragment new AsyncLayoutInflater(this) .inflate( R.layout.dialer, null, new AsyncLayoutInflater.OnInflateFinishedListener() { @Override public void onInflateFinished( @NonNull View view, int resid, @Nullable ViewGroup parent) { LinearLayout fragmentContainer = findViewById(R.id.fragmentContainer); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); fragmentContainer.addView(view, params); initUI(view); mInterfaceLoaded = true; if (mAddressToCallOnLayoutReady != null) { mAddress.setText(mAddressToCallOnLayoutReady); mAddressToCallOnLayoutReady = null; } } }); if (isTablet()) { findViewById(R.id.fragmentContainer2).setVisibility(View.GONE); } mListener = new CoreListenerStub() { @Override public void onCallStateChanged( Core core, Call call, Call.State state, String message) { updateLayout(); } @Override public void onVersionUpdateCheckResultReceived( Core core, VersionUpdateCheckResult result, String version, String url) { if (result == VersionUpdateCheckResult.NewVersionAvailable) { final String urlToUse = url; final String versionAv = version; LinphoneUtils.dispatchOnUIThreadAfter( new Runnable() { @Override public void run() { AlertDialog.Builder builder = new AlertDialog.Builder(DialerActivity.this); builder.setMessage( getString(R.string.update_available) + ": " + versionAv); builder.setCancelable(false); builder.setNeutralButton( getString(R.string.ok), new DialogInterface.OnClickListener() { @Override public void onClick( DialogInterface dialogInterface, int i) { if (urlToUse != null) { Intent urlIntent = new Intent( Intent.ACTION_VIEW); urlIntent.setData( Uri.parse(urlToUse)); startActivity(urlIntent); } } }); builder.show(); } }, 1000); } } }; // On dialer we ask for all permissions mPermissionsToHave = new String[] { // This one is to allow floating notifications Manifest.permission.SYSTEM_ALERT_WINDOW, // Required starting Android 9 to be able to start a foreground service "android.permission.FOREGROUND_SERVICE", Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CONTACTS }; mIsTransfer = false; if (getIntent() != null) { mIsTransfer = getIntent().getBooleanExtra("isTransfer", false); } handleIntentParams(getIntent()); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); handleIntentParams(intent); if (intent != null) { mIsTransfer = intent.getBooleanExtra("isTransfer", mIsTransfer); if (mAddress != null && intent.getStringExtra("SipUri") != null) { mAddress.setText(intent.getStringExtra("SipUri")); } } } @Override protected void onResume() { super.onResume(); mDialerSelected.setVisibility(View.VISIBLE); Core core = LinphoneManager.getCore(); if (core != null) { core.addListener(mListener); } if (mInterfaceLoaded) { updateLayout(); enableVideoPreviewIfTablet(true); } } @Override protected void onPause() { enableVideoPreviewIfTablet(false); Core core = LinphoneManager.getCore(); if (core != null) { core.removeListener(mListener); } super.onPause(); } @Override protected void onDestroy() { if (mInterfaceLoaded) { mAddress = null; mStartCall = null; mAddCall = null; mTransferCall = null; mAddContact = null; mBackToCall = null; } if (mListener != null) mListener = null; super.onDestroy(); } @Override protected void onStart() { super.onStart(); if (getResources().getBoolean(R.bool.check_for_update_when_app_starts)) { checkForUpdate(); } } private void checkForUpdate() { String url = LinphonePreferences.instance().getCheckReleaseUrl(); if (url != null && !url.isEmpty()) { int lastTimestamp = LinphonePreferences.instance().getLastCheckReleaseTimestamp(); int currentTimeStamp = (int) System.currentTimeMillis(); int interval = getResources().getInteger(R.integer.time_between_update_check); // 24h if (lastTimestamp == 0 || currentTimeStamp - lastTimestamp >= interval) { LinphoneManager.getCore().checkForUpdate(BuildConfig.VERSION_NAME); LinphonePreferences.instance().setLastCheckReleaseTimestamp(currentTimeStamp); } } } private void initUI(View view) { mAddress = view.findViewById(R.id.address); mAddress.setAddressListener(this); EraseButton erase = view.findViewById(R.id.erase); erase.setAddressWidget(mAddress); mStartCall = view.findViewById(R.id.start_call); mStartCall.setAddressWidget(mAddress); mAddCall = view.findViewById(R.id.add_call); mAddCall.setAddressWidget(mAddress); mTransferCall = view.findViewById(R.id.transfer_call); mTransferCall.setAddressWidget(mAddress); mTransferCall.setIsTransfer(true); mAddContact = view.findViewById(R.id.add_contact); mAddContact.setEnabled(false); mAddContact.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(DialerActivity.this, ContactsActivity.class); intent.putExtra("EditOnClick", true); intent.putExtra("SipAddress", mAddress.getText().toString()); startActivity(intent); } }); mBackToCall = view.findViewById(R.id.back_to_call); mBackToCall.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { goBackToCall(); } }); if (getIntent() != null) { mAddress.setText(getIntent().getStringExtra("SipUri")); } setUpNumpad(view); updateLayout(); enableVideoPreviewIfTablet(true); } private void enableVideoPreviewIfTablet(boolean enable) { Core core = LinphoneManager.getCore(); TextureView preview = findViewById(R.id.video_preview); ImageView changeCamera = findViewById(R.id.video_preview_change_camera); if (preview != null && changeCamera != null && core != null) { if (enable && isTablet() && LinphonePreferences.instance().isVideoPreviewEnabled()) { preview.setVisibility(View.VISIBLE); core.setNativePreviewWindowId(preview); core.enableVideoPreview(true); if (core.getVideoDevicesList().length > 1) { changeCamera.setVisibility(View.VISIBLE); changeCamera.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { LinphoneManager.getCallManager().switchCamera(); } }); } } else { preview.setVisibility(View.GONE); changeCamera.setVisibility(View.GONE); core.setNativePreviewWindowId(null); core.enableVideoPreview(false); } } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putSerializable("isTransfer", mIsTransfer); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); mIsTransfer = savedInstanceState.getBoolean("isTransfer"); } @Override public void onAddressChanged() { mAddContact.setEnabled(!mAddress.getText().toString().isEmpty()); } private void updateLayout() { Core core = LinphoneManager.getCore(); if (core == null) { return; } boolean atLeastOneCall = core.getCallsNb() > 0; mStartCall.setVisibility(atLeastOneCall ? View.GONE : View.VISIBLE); mAddContact.setVisibility(atLeastOneCall ? View.GONE : View.VISIBLE); mAddContact.setEnabled(!mAddress.getText().toString().isEmpty()); if (!atLeastOneCall) { if (core.getVideoActivationPolicy().getAutomaticallyInitiate()) { mStartCall.setImageResource(R.drawable.call_video_start); } else { mStartCall.setImageResource(R.drawable.call_audio_start); } } mBackToCall.setVisibility(atLeastOneCall ? View.VISIBLE : View.GONE); mAddCall.setVisibility(atLeastOneCall && !mIsTransfer ? View.VISIBLE : View.GONE); mTransferCall.setVisibility(atLeastOneCall && mIsTransfer ? View.VISIBLE : View.GONE); } private void handleIntentParams(Intent intent) { if (intent == null) return; String action = intent.getAction(); String addressToCall = null; if (ACTION_CALL_LINPHONE.equals(action) && (intent.getStringExtra("NumberToCall") != null)) { String numberToCall = intent.getStringExtra("NumberToCall"); Log.i("[Dialer] ACTION_CALL_LINPHONE with number: " + numberToCall); LinphoneManager.getCallManager().newOutgoingCall(numberToCall, null); } else { Uri uri = intent.getData(); if (uri != null) { Log.i("[Dialer] Intent data is: " + uri.toString()); if (Intent.ACTION_CALL.equals(action)) { String dataString = intent.getDataString(); try { addressToCall = URLDecoder.decode(dataString, "UTF-8"); } catch (UnsupportedEncodingException e) { Log.e("[Dialer] Unable to decode URI " + dataString); addressToCall = dataString; } if (addressToCall.startsWith("sip:")) { addressToCall = addressToCall.substring("sip:".length()); } else if (addressToCall.startsWith("tel:")) { addressToCall = addressToCall.substring("tel:".length()); } Log.i("[Dialer] ACTION_CALL with number: " + addressToCall); } else { addressToCall = ContactsManager.getInstance() .getAddressOrNumberForAndroidContact(getContentResolver(), uri); Log.i("[Dialer] " + action + " with number: " + addressToCall); } } else { Log.w("[Dialer] Intent data is null for action " + action); } } if (addressToCall != null) { if (mAddress != null) { mAddress.setText(addressToCall); } else { mAddressToCallOnLayoutReady = addressToCall; } } } private void setUpNumpad(View view) { if (view == null) return; for (Digit v : retrieveChildren((ViewGroup) view, Digit.class)) { v.setAddressWidget(mAddress); } } private Collection retrieveChildren(ViewGroup viewGroup, Class clazz) { final Collection views = new ArrayList<>(); for (int i = 0; i < viewGroup.getChildCount(); i++) { View v = viewGroup.getChildAt(i); if (v instanceof ViewGroup) { views.addAll(retrieveChildren((ViewGroup) v, clazz)); } else { if (clazz.isInstance(v)) views.add(clazz.cast(v)); } } return views; } }