// -*- mode: java; c-basic-offset: 2; -*- // Copyright 2009-2011 Google, All Rights reserved // Copyright 2011-2012 MIT, All rights reserved // Released under the Apache License, Version 2.0 // http://www.apache.org/licenses/LICENSE-2.0 package com.google.appinventor.components.runtime; import com.google.appinventor.components.annotations.DesignerComponent; import com.google.appinventor.components.annotations.DesignerProperty; import com.google.appinventor.components.annotations.PropertyCategory; import com.google.appinventor.components.annotations.SimpleEvent; import com.google.appinventor.components.annotations.SimpleFunction; import com.google.appinventor.components.annotations.SimpleObject; import com.google.appinventor.components.annotations.SimpleProperty; import com.google.appinventor.components.annotations.UsesPermissions; import com.google.appinventor.components.common.ComponentCategory; import com.google.appinventor.components.common.PropertyTypeConstants; import com.google.appinventor.components.common.YaVersion; import com.google.appinventor.components.runtime.util.PhoneCallUtil; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.telephony.TelephonyManager; /** * Component for making a phone call to a programatically-specified number. * * TODO(markf): Note that the initial carrier for Android phones only supports 3 participants * in a conference call, so that's all that the current implementation of this * component supports. In the future we can generalize this to more participants. * * @author markf@google.com (Mark Friedman) * @author rekygx@gmail.com (Xian Gao) */ @DesignerComponent(version = YaVersion.PHONECALL_COMPONENT_VERSION, description = "<p>A non-visible component that makes a phone call to " + "the number specified in the <code>PhoneNumber</code> property, which " + "can be set either in the Designer or Blocks Editor. The component " + "has a <code>MakePhoneCall</code> method, enabling the program to launch " + "a phone call.</p>" + "<p>Often, this component is used with the <code>ContactPicker</code> " + "component, which lets the user select a contact from the ones stored " + "on the phone and sets the <code>PhoneNumber</code> property to the " + "contact's phone number.</p>" + "<p>To directly specify the phone number (e.g., 650-555-1212), set " + "the <code>PhoneNumber</code> property to a Text with the specified " + "digits (e.g., \"6505551212\"). Dashes, dots, and parentheses may be " + "included (e.g., \"(650)-555-1212\") but will be ignored; spaces may " + "not be included.</p>", category = ComponentCategory.SOCIAL, nonVisible = true, iconName = "images/phoneCall.png") @SimpleObject @UsesPermissions(permissionNames = "android.permission.CALL_PHONE, android.permission.READ_PHONE_STATE, android.permission.PROCESS_OUTGOING_CALLS") public class PhoneCall extends AndroidNonvisibleComponent implements Component, OnDestroyListener { private String phoneNumber; private final Context context; private final CallStateReceiver callStateReceiver; /** * Creates a Phone Call component. * * @param container container, component will be placed in */ public PhoneCall(ComponentContainer container) { super(container.$form()); context = container.$context(); form.registerForOnDestroy(this); PhoneNumber(""); callStateReceiver = new CallStateReceiver(); registerCallStateMonitor(); } /** * PhoneNumber property getter method. */ @SimpleProperty( category = PropertyCategory.BEHAVIOR) public String PhoneNumber() { return phoneNumber; } /** * PhoneNumber property setter method: sets a phone number to call. * * @param phoneNumber a phone number to call */ @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING, defaultValue = "") @SimpleProperty public void PhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; } /** * Makes a phone call using the number in the PhoneNumber property. */ @SimpleFunction public void MakePhoneCall() { PhoneCallUtil.makePhoneCall(context, phoneNumber); } /** * Event indicating that a phone call has started. * status: 1:incoming call is ringing; 2:outgoing call is dialled. * * @param status 1:incoming call is ringing; 2:outgoing call is dialled. * @param phoneNumber incoming/outgoing call phone number */ @SimpleEvent( description = "Event indicating that a phonecall has started." + " If status is 1, incoming call is ringing; " + "if status is 2, outgoing call is dialled. " + "phoneNumber is the incoming/outgoing phone number.") public void PhoneCallStarted(int status, String phoneNumber) { // invoke the application's "PhoneCallStarted" event handler. EventDispatcher.dispatchEvent(this, "PhoneCallStarted", status, phoneNumber); } /** * Event indicating that a phone call has ended. * status: 1:incoming call is missed or rejected; 2:incoming call is answered before hanging up; 3:Outgoing call is hung up. * * @param status 1:incoming call is missed or rejected; 2:incoming call is answered before hanging up; 3:Outgoing call is hung up. * @param phoneNumber ended call phone number */ @SimpleEvent( description = "Event indicating that a phone call has ended. " + "If status is 1, incoming call is missed or rejected; " + "if status is 2, incoming call is answered before hanging up; " + "if status is 3, outgoing call is hung up. " + "phoneNumber is the ended call phone number.") public void PhoneCallEnded(int status, String phoneNumber) { // invoke the application's "PhoneCallEnded" event handler. EventDispatcher.dispatchEvent(this, "PhoneCallEnded", status, phoneNumber); } /** * Event indicating that an incoming phone call is answered. * * @param phoneNumber incoming call phone number */ @SimpleEvent( description = "Event indicating that an incoming phone call is answered. " + "phoneNumber is the incoming call phone number.") public void IncomingCallAnswered(String phoneNumber) { // invoke the application's "IncomingCallAnswered" event handler. EventDispatcher.dispatchEvent(this, "IncomingCallAnswered", phoneNumber); } /** * BroadcastReceiver for incomming/outgoing phonecall state changes * */ private class CallStateReceiver extends BroadcastReceiver { private int status; // 0:undetermined, 1:incoming ringed, 2:outgoing dialled, 3: incoming answered private String number; // phone call number public CallStateReceiver() { status = 0; number = ""; } @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if(TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)){ String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE); if(TelephonyManager.EXTRA_STATE_RINGING.equals(state)){ // Incoming call rings status = 1; number = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER); PhoneCallStarted(1, number); }else if(TelephonyManager.EXTRA_STATE_OFFHOOK.equals(state)){ // Call off-hook if(status == 1){ // Incoming call answered status = 3; IncomingCallAnswered(number); } }else if(TelephonyManager.EXTRA_STATE_IDLE.equals(state)){ // Incomming/Outgoing Call ends if(status == 1){ // Incoming Missed or Rejected PhoneCallEnded(1, number); }else if(status == 3){ // Incoming Answer Ended PhoneCallEnded(2, number); }else if(status == 2){ // Outgoing Ended PhoneCallEnded(3, number); } status = 0; number = ""; } }else if(Intent.ACTION_NEW_OUTGOING_CALL.equals(action)){ // Outgoing call dialled status = 2; number = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); PhoneCallStarted(2, number); } } } /** * Registers phonecall state monitor */ private void registerCallStateMonitor(){ IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_NEW_OUTGOING_CALL); intentFilter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); context.registerReceiver(callStateReceiver, intentFilter); } /** * Unregisters phonecall state monitor */ private void unregisterCallStateMonitor(){ context.unregisterReceiver(callStateReceiver); } @Override public void onDestroy() { unregisterCallStateMonitor(); } }