package org.commcare.android.util; import java.util.HashSet; import java.util.Hashtable; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.Vector; import org.commcare.android.database.user.models.User; import org.commcare.android.javarosa.AndroidLogger; import org.commcare.dalvik.R; import org.commcare.suite.model.Detail; import org.commcare.suite.model.Entry; import org.commcare.suite.model.SessionDatum; import org.commcare.suite.model.Suite; import org.commcare.suite.model.Text; import org.commcare.util.CommCareSession; import org.javarosa.core.model.condition.EvaluationContext; import org.javarosa.core.model.instance.TreeReference; import org.javarosa.core.services.Logger; import android.content.Context; import android.content.Intent; import android.os.AsyncTask; import android.telephony.PhoneNumberUtils; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.util.Pair; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; import android.widget.Toast; /** * @author ctsims * */ public class CallInPhoneListener extends PhoneStateListener { private Context context; private AndroidCommCarePlatform platform; private Hashtable<String, String[]> cachedNumbers; private Toast currentToast; private Timer toastTimer; private boolean running = false; public CallInPhoneListener(Context context, AndroidCommCarePlatform platform) { this.context = context; this.platform = platform; cachedNumbers = new Hashtable<String, String[]>(); toastTimer = new Timer("toastTimer"); } /* * (non-Javadoc) * @see android.telephony.PhoneStateListener#onCallStateChanged(int, java.lang.String) */ @Override public void onCallStateChanged(int state, String incomingNumber) { super.onCallStateChanged(state, incomingNumber); if(state == TelephonyManager.CALL_STATE_RINGING) { String caller = getCaller(incomingNumber); if(caller != null) { LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View layout = inflater.inflate(R.layout.call_toast, null); TextView textView = (TextView)layout.findViewById(R.id.incoming_call_text); textView.setText("Incoming Call From: " + caller); currentToast = new Toast(context); currentToast.setDuration(Toast.LENGTH_LONG); currentToast.setView(layout); currentToast.setGravity(Gravity.BOTTOM, 0,0); running = true; startToastLoop(); } } else { running = false; } } public void startToastLoop() { synchronized(toastTimer) { toastTimer.schedule( new TimerTask() { int runtimes = 0; public void run() { if(runtimes > 100 || running == false) { this.cancel(); } else { runtimes++; currentToast.show(); } } }, 0, 200); } } public void startCache() { AsyncTask<Void, Void, Void> loader = new AsyncTask<Void, Void, Void>() { /* * (non-Javadoc) * @see android.os.AsyncTask#doInBackground(java.lang.Object[]) */ @Override protected Void doInBackground(Void... params) { try { synchronized(cachedNumbers) { Hashtable<String,Pair<String, TreeReference>> detailSources = new Hashtable<String,Pair<String, TreeReference>>(); Set<Detail> details = new HashSet<Detail>(); //To fan this out, we first need to find the appropriate long detail screens //then determine what nodeset to use to iterate over it //First, collect the details we need to use. for(Suite s : platform.getInstalledSuites() ){ for(Entry e : s.getEntries().values()) { //We won't bother trying to handle the situation where there's more than one //thing to collect, just yet. In the future, we'll fan out the whole thing. if(e.getSessionDataReqs().size() !=1) { continue; } SessionDatum datum = e.getSessionDataReqs().firstElement(); String detailId = datum.getLongDetail(); if(detailId == null) { continue; } for(String form : s.getDetail(detailId).getTemplateForms()) { if("phone".equals(form)) { //Found some numbers! //Check to see if we've already got a detail for this if(detailSources.containsKey(detailId)) { //Ok. So in the future we should possibly run all of the details //we can where ID's don't match, but for now, we'll stick with the smallest //set of predicates (most common use case is "Mine" v. "all" cases and such) TreeReference thisRef = datum.getNodeset(); TreeReference existing = detailSources.get(detailId).second; if(CommCareUtil.countPreds(thisRef) < CommCareUtil.countPreds(existing)) { detailSources.put(detailId, new Pair(e.getCommandId(), thisRef)); } } //Otherwise, grab the reference and save it. else { detailSources.put(detailId, new Pair(e.getCommandId(), datum.getNodeset())); details.add(s.getDetail(detailId)); } //We don't need to worry about any other items in this detail, so finish up. break; } } } } //Ok, so now we have a set of details and the nodesets they use. Let's pull out some numbers //Go through each detail type one by one for(Detail d : details) { try{ //Create an evaluation context (should only really need to handle the high level stuff) EvaluationContext ec = getEC(detailSources.get(d.getId()).first); TreeReference nodesetSource = detailSources.get(d.getId()).second; Vector<TreeReference> references =ec .expandReference(nodesetSource); Set<Integer> phoneIds = new HashSet<Integer>(); String[] forms = d.getTemplateForms(); for(int i = 0 ; i < forms.length ; ++i) { if("phone".equals(forms[i])) { //Get all the numbers we'll want phoneIds.add(i); } } for(TreeReference r : references) { EvaluationContext childContext = new EvaluationContext(ec, r); //TODO: Generate a whole Session that could be used to start up form entry //based on this somehow? String name = d.getTitle().getText().evaluate(childContext); for(int i : phoneIds) { String number = ((Text) d.getFields()[i].getTemplate()).evaluate(childContext); if(number != "") { cachedNumbers.put(number, new String[] {name}); } } } } catch(Exception e){ Logger.log(AndroidLogger.TYPE_ERROR_DESIGN, "Caching failed with exception: " + e.getMessage()); } } System.out.println("Caching Complete"); return null; } } catch(SessionUnavailableException sue) { //We got logged out in the middle of return null; } } private EvaluationContext getEC(String commandId) { CommCareSession session = new CommCareSession(platform); session.setCommand(commandId); return session.getEvaluationContext(new CommCareInstanceInitializer(session)); } }; loader.execute(); } public String getCaller(String incomingNumber) { synchronized(cachedNumbers) { for(String number : cachedNumbers.keySet()) { if(PhoneNumberUtils.compare(context, number, incomingNumber)) { return cachedNumbers.get(number)[0]; } } } return null; } public Intent getDetailIntent(Context context, String incomingNumber) { // synchronized(cachedNumbers) { // for(String number : cachedNumbers.keySet()) { // if(PhoneNumberUtils.compare(context, number, incomingNumber)) { // String[] details = cachedNumbers.get(number); // // Intent i = new Intent(context, ReferenceDetailActivity.class); // i.putExtra(CommCareSession.STATE_COMMAND_ID, details[2]); // i.putExtra(CommCareSession.STATE_CASE_ID, details[1]); // i.putExtra(ReferenceDetailActivity.IS_DEAD_END, true); // return i; // } // } // } return null; } }