Adding interfaces for phone number service.

- Fetch image url if present.

Bug: 10359919
Change-Id: Idf2ff5405255f1e90a383902762585ae68249dbe
diff --git a/InCallUI/AndroidManifest.xml b/InCallUI/AndroidManifest.xml
index 0b603e0..3196dba 100644
--- a/InCallUI/AndroidManifest.xml
+++ b/InCallUI/AndroidManifest.xml
@@ -22,6 +22,7 @@
 
     <uses-permission android:name="android.permission.READ_CONTACTS" />
     <uses-permission android:name="android.permission.VIBRATE"/>
+    <uses-permission android:name="android.permission.INTERNET" />
 
     <application
             android:name="InCallApp"
diff --git a/InCallUI/src/com/android/incallui/AnswerFragment.java b/InCallUI/src/com/android/incallui/AnswerFragment.java
index d1a1df0..aea80ec 100644
--- a/InCallUI/src/com/android/incallui/AnswerFragment.java
+++ b/InCallUI/src/com/android/incallui/AnswerFragment.java
@@ -33,8 +33,8 @@
 /**
  *
  */
-public class AnswerFragment extends BaseFragment<AnswerPresenter> implements
-        GlowPadWrapper.AnswerListener, AnswerPresenter.AnswerUi {
+public class AnswerFragment extends BaseFragment<AnswerPresenter, AnswerPresenter.AnswerUi>
+        implements GlowPadWrapper.AnswerListener, AnswerPresenter.AnswerUi {
 
     /**
      * The popup showing the list of canned responses.
@@ -57,13 +57,17 @@
     }
 
     @Override
+    AnswerPresenter.AnswerUi getUi() {
+        return this;
+    }
+
+    @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
         final GlowPadWrapper glowPad = (GlowPadWrapper) inflater.inflate(R.layout.answer_fragment,
                 container, false);
 
         glowPad.setAnswerListener(this);
-        getPresenter().onUiReady(this);
 
         return glowPad;
     }
diff --git a/InCallUI/src/com/android/incallui/BaseFragment.java b/InCallUI/src/com/android/incallui/BaseFragment.java
index a88f840..0f3d6b4 100644
--- a/InCallUI/src/com/android/incallui/BaseFragment.java
+++ b/InCallUI/src/com/android/incallui/BaseFragment.java
@@ -17,21 +17,38 @@
 package com.android.incallui;
 
 import android.app.Fragment;
+import android.os.Bundle;
+import android.view.View;
+
+import com.android.internal.util.Preconditions;
 
 /**
  *
  */
-public abstract class BaseFragment<T extends Presenter> extends Fragment {
+public abstract class BaseFragment<T extends Presenter<U>, U extends Ui> extends Fragment {
 
     private T mPresenter;
 
-    protected BaseFragment() {
-        this.mPresenter = createPresenter();
-    }
-
     abstract T createPresenter();
 
+    abstract U getUi();
+
+    protected BaseFragment() {
+        mPresenter = createPresenter();
+    }
+
+    /**
+     * Presenter will be available after onActivityCreated().
+     *
+     * @return The presenter associated with this fragment.
+     */
     public T getPresenter() {
         return mPresenter;
     }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        mPresenter.onUiReady(getUi());
+    }
 }
diff --git a/InCallUI/src/com/android/incallui/CallButtonFragment.java b/InCallUI/src/com/android/incallui/CallButtonFragment.java
index 21bfb1c..15d0446 100644
--- a/InCallUI/src/com/android/incallui/CallButtonFragment.java
+++ b/InCallUI/src/com/android/incallui/CallButtonFragment.java
@@ -16,9 +16,7 @@
 
 package com.android.incallui;
 
-import android.content.Context;
 import android.graphics.drawable.LayerDrawable;
-import android.media.AudioManager;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.Menu;
@@ -37,9 +35,10 @@
 /**
  * Fragment for call control buttons
  */
-public class CallButtonFragment extends BaseFragment<CallButtonPresenter>
-        implements CallButtonPresenter.CallButtonUi, OnMenuItemClickListener,
-                OnDismissListener, View.OnClickListener, CompoundButton.OnCheckedChangeListener {
+public class CallButtonFragment
+        extends BaseFragment<CallButtonPresenter, CallButtonPresenter.CallButtonUi>
+        implements CallButtonPresenter.CallButtonUi, OnMenuItemClickListener, OnDismissListener,
+        View.OnClickListener, CompoundButton.OnCheckedChangeListener {
 
     private ToggleButton mMuteButton;
     private ToggleButton mAudioButton;
@@ -61,6 +60,11 @@
     }
 
     @Override
+    CallButtonPresenter.CallButtonUi getUi() {
+        return this;
+    }
+
+    @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
     }
@@ -115,8 +119,8 @@
     }
 
     @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
-        getPresenter().onUiReady(this);
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
 
         // set the buttons
         updateAudioButtons(getPresenter().getSupportedAudio());
diff --git a/InCallUI/src/com/android/incallui/CallCardFragment.java b/InCallUI/src/com/android/incallui/CallCardFragment.java
index 18b51d9..3477438 100644
--- a/InCallUI/src/com/android/incallui/CallCardFragment.java
+++ b/InCallUI/src/com/android/incallui/CallCardFragment.java
@@ -31,12 +31,13 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.android.incalluibind.ServiceFactory;
 import com.android.services.telephony.common.Call;
 
 /**
  * Fragment for call card.
  */
-public class CallCardFragment extends BaseFragment<CallCardPresenter>
+public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPresenter.CallCardUi>
         implements CallCardPresenter.CallCardUi {
 
     // Primary caller info
@@ -56,21 +57,36 @@
     private float mDensity;
 
     @Override
+    CallCardPresenter.CallCardUi getUi() {
+        return this;
+    }
+
+    @Override
     CallCardPresenter createPresenter() {
         return new CallCardPresenter();
     }
 
     @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        getPresenter().init(ServiceFactory.newPhoneNumberService(getActivity()));
+    }
+
+    @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
+        super.onCreateView(inflater, container, savedInstanceState);
+
         mDensity = getResources().getDisplayMetrics().density;
 
         return inflater.inflate(R.layout.call_card, container, false);
     }
 
-
     @Override
     public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+
         mPhoneNumber = (TextView) view.findViewById(R.id.phoneNumber);
         mName = (TextView) view.findViewById(R.id.name);
         mNumberLabel = (TextView) view.findViewById(R.id.label);
@@ -78,10 +94,6 @@
         mPhoto = (ImageView) view.findViewById(R.id.photo);
         mCallStateLabel = (TextView) view.findViewById(R.id.callStateLabel);
         mElapsedTime = (TextView) view.findViewById(R.id.elapsedTime);
-
-        // This method call will begin the callbacks on CallCardUi. We need to ensure
-        // everything needed for the callbacks is set up before this is called.
-        getPresenter().onUiReady(this);
     }
 
     @Override
@@ -100,6 +112,16 @@
     }
 
     @Override
+    public void setName(String name) {
+        mName.setText(name);
+    }
+
+    @Override
+    public void setImage(Bitmap image) {
+        setDrawableToImageView(mPhoto, new BitmapDrawable(getResources(), image));
+    }
+
+    @Override
     public void setPrimary(String number, String name, boolean nameIsNumber, String label,
             Drawable photo, boolean isConference) {
 
diff --git a/InCallUI/src/com/android/incallui/CallCardPresenter.java b/InCallUI/src/com/android/incallui/CallCardPresenter.java
index bf27a1b..7eec3fb 100644
--- a/InCallUI/src/com/android/incallui/CallCardPresenter.java
+++ b/InCallUI/src/com/android/incallui/CallCardPresenter.java
@@ -17,6 +17,14 @@
 package com.android.incallui;
 
 import android.graphics.drawable.Drawable;
+import android.content.ContentUris;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.provider.ContactsContract.Contacts;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 
@@ -25,20 +33,27 @@
 import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
 import com.android.incallui.InCallPresenter.InCallState;
 import com.android.incallui.InCallPresenter.InCallStateListener;
-
+import com.android.incallui.service.PhoneNumberService;
+import com.android.incallui.util.HttpFetcher;
+import com.android.incalluibind.ServiceFactory;
 import com.android.services.telephony.common.AudioMode;
 import com.android.services.telephony.common.Call;
 import com.android.services.telephony.common.Call.DisconnectCause;
 
+import java.io.IOException;
+
 /**
  * Presenter for the Call Card Fragment.
+ * <p>
  * This class listens for changes to InCallState and passes it along to the fragment.
  */
 public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
         implements InCallStateListener, AudioModeListener, ContactInfoCacheCallback {
 
+    private static final String TAG = CallCardPresenter.class.getSimpleName();
     private static final long CALL_TIME_UPDATE_INTERVAL = 1000; // in milliseconds
 
+    private PhoneNumberService mPhoneNumberService;
     private AudioModeProvider mAudioModeProvider;
     private ContactInfoCache mContactInfoCache;
     private Call mPrimary;
@@ -58,6 +73,10 @@
         });
     }
 
+    public void init(PhoneNumberService phoneNumberService) {
+        mPhoneNumberService = phoneNumberService;
+    }
+
     @Override
     public void onUiReady(CallCardUi ui) {
         super.onUiReady(ui);
@@ -81,6 +100,7 @@
 
     @Override
     public void onStateChange(InCallState state, CallList callList) {
+        Logger.d(TAG, "onStateChange()");
         final CallCardUi ui = getUi();
         if (ui == null) {
             return;
@@ -233,10 +253,12 @@
         if (mPrimary != null && mPrimary.getCallId() == callId) {
             mPrimaryContactInfo = entry;
             updatePrimaryDisplayInfo();
+            lookupPhoneNumber(mPrimary.getNumber());
         }
         if (mSecondary != null && mSecondary.getCallId() == callId) {
             mSecondaryContactInfo = entry;
             updateSecondaryDisplayInfo();
+            // TODO(klp): investigate reverse lookup for secondary call.
         }
 
     }
@@ -260,6 +282,59 @@
 
     }
 
+    public void lookupPhoneNumber(String phoneNumber) {
+        if (mPhoneNumberService != null) {
+            mPhoneNumberService.getPhoneNumberInfo(phoneNumber,
+                    new PhoneNumberService.PhoneNumberServiceListener() {
+                        @Override
+                        public void onPhoneNumberInfoComplete(
+                                final PhoneNumberService.PhoneNumberInfo info) {
+                            if (info == null) {
+                                return;
+                            }
+                            // TODO(klp): Ui is sometimes null due to something being shutdown.
+                            if (getUi() != null) {
+                                if (info.getName() != null) {
+                                    getUi().setName(info.getName());
+                                }
+
+                                if (info.getImageUrl() != null) {
+                                    fetchImage(info.getImageUrl());
+                                }
+                            }
+                        }
+                    });
+        }
+    }
+
+    private void fetchImage(final String url) {
+        if (url != null) {
+            new AsyncTask<Void, Void, Bitmap>() {
+
+                @Override
+                protected Bitmap doInBackground(Void... params) {
+                    // Fetch the image
+                    try {
+                        final byte[] image = HttpFetcher.getRequestAsByteArray(url);
+                        return BitmapFactory.decodeByteArray(image, 0, image.length);
+                    } catch (IOException e) {
+                        Logger.e(TAG, "Unable to download/decode photo.", e);
+                    }
+                    return null;
+                }
+
+                @Override
+                protected void onPostExecute(Bitmap bitmap) {
+                    // TODO(klp): same as above, figure out why it's null.
+                    if (getUi() != null) {
+                        getUi().setImage(bitmap);
+                    }
+                }
+
+            }.execute();
+        }
+    }
+
     /**
      * Gets the name to display for the call.
      */
@@ -310,5 +385,7 @@
         void setSecondary(boolean show, String name, String label, Drawable photo);
         void setCallState(int state, Call.DisconnectCause cause, boolean bluetoothOn);
         void setPrimaryCallElapsedTime(boolean show, String duration);
+        void setName(String name);
+        void setImage(Bitmap bitmap);
     }
 }
diff --git a/InCallUI/src/com/android/incallui/DialpadFragment.java b/InCallUI/src/com/android/incallui/DialpadFragment.java
index 6be923c..b996e4f 100644
--- a/InCallUI/src/com/android/incallui/DialpadFragment.java
+++ b/InCallUI/src/com/android/incallui/DialpadFragment.java
@@ -31,7 +31,7 @@
 /**
  * Fragment for call control buttons
  */
-public class DialpadFragment extends BaseFragment<DialpadPresenter>
+public class DialpadFragment extends BaseFragment<DialpadPresenter, DialpadPresenter.DialpadUi>
         implements DialpadPresenter.DialpadUi, View.OnTouchListener, View.OnKeyListener,
         View.OnHoverListener, View.OnClickListener {
 
@@ -164,6 +164,11 @@
     }
 
     @Override
+    DialpadPresenter.DialpadUi getUi() {
+        return this;
+    }
+
+    @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
     }
@@ -181,7 +186,6 @@
 
             setupKeypad(parent);
         }
-        getPresenter().onUiReady(this);
         return parent;
     }
 
diff --git a/InCallUI/src/com/android/incallui/service/PhoneNumberService.java b/InCallUI/src/com/android/incallui/service/PhoneNumberService.java
new file mode 100644
index 0000000..1e0cb95
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/service/PhoneNumberService.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.incallui.service;
+
+/**
+ *
+ */
+public interface PhoneNumberService {
+    public void getPhoneNumberInfo(String phoneNumber, PhoneNumberServiceListener listener);
+
+    public interface PhoneNumberServiceListener {
+
+        /**
+         * Callback when a phone number has been looked up.
+         *
+         * @param info The looked up information.  Or (@literal null} if there are no results.
+         */
+        public void onPhoneNumberInfoComplete(PhoneNumberInfo info);
+    }
+
+    public interface PhoneNumberInfo {
+        public String getName();
+        public String getPhoneNumber();
+        public String getImageUrl();
+    }
+}
diff --git a/InCallUI/src/com/android/incallui/util/HttpFetcher.java b/InCallUI/src/com/android/incallui/util/HttpFetcher.java
new file mode 100644
index 0000000..b54bf71
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/util/HttpFetcher.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.incallui.util;
+
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.google.common.io.Closeables;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+/**
+ * Utility for making http requests.
+ */
+public class HttpFetcher {
+
+    private static final String TAG = HttpFetcher.class.getSimpleName();
+
+    /**
+     * Send a http request to the given url.
+     *
+     * @param urlString The url to request.
+     * @return The response body as a byte array.  Or {@literal null} if status code is not 2xx.
+     * @throws java.io.IOException when an error occurs.
+     */
+    public static byte[] getRequestAsByteArray(String urlString) throws IOException {
+        Log.d(TAG, "fetching " + urlString);
+        HttpURLConnection conn = null;
+        InputStream is = null;
+        boolean isError = false;
+        final long start = SystemClock.uptimeMillis();
+        try {
+            final URL url = new URL(urlString);
+            conn = (HttpURLConnection) url.openConnection();
+            Log.d(TAG, "response code: " + conn.getResponseCode());
+            // All 2xx codes are successful.
+            if (conn.getResponseCode() / 100 == 2) {
+                is = conn.getInputStream();
+            } else {
+                is = conn.getErrorStream();
+                isError = true;
+            }
+
+            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            final byte[] buffer = new byte[1024];
+            int bytesRead;
+
+            while ((bytesRead = is.read(buffer)) != -1) {
+                baos.write(buffer, 0, bytesRead);
+            }
+
+            if (isError) {
+                handleBadResponse(urlString, baos.toByteArray());
+                return null;
+            }
+
+            final byte[] response = baos.toByteArray();
+            Log.d(TAG, "received " + response.length + " bytes");
+            final long end = SystemClock.uptimeMillis();
+            Log.d(TAG, "fetch took " + (end - start) + " ms");
+            return response;
+        } finally {
+            Closeables.closeQuietly(is);
+            if (conn != null) {
+                conn.disconnect();
+            }
+        }
+    }
+
+    /**
+     * Send a http request to the given url.
+     *
+     * @param urlString The url to request.
+     * @return The response body as a String. Or {@literal null} if status code is not 2xx.
+     * @throws java.io.IOException when an error occurs.
+     */
+    public static String getRequestAsString(String urlString) throws IOException {
+        final byte[] byteArr = getRequestAsByteArray(urlString);
+        if (byteArr == null) {
+            // Encountered error response... just return.
+            return null;
+        }
+        final String response = new String(byteArr);
+        Log.d(TAG, "response body: ");
+        Log.d(TAG, response);
+        return response;
+    }
+
+    private static void handleBadResponse(String url, byte[] response) {
+        Log.w(TAG, "Got bad response code from url: " + url);
+        Log.w(TAG, new String(response));
+    }
+}
diff --git a/InCallUI/src/com/android/incalluibind/ServiceFactory.java b/InCallUI/src/com/android/incalluibind/ServiceFactory.java
new file mode 100644
index 0000000..7191f14
--- /dev/null
+++ b/InCallUI/src/com/android/incalluibind/ServiceFactory.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.incalluibind;
+
+import android.content.Context;
+
+import com.android.incallui.service.PhoneNumberService;
+
+/**
+ * Default static binder for services.
+ */
+public class ServiceFactory {
+
+    public static PhoneNumberService newPhoneNumberService(Context context) {
+        // no phone number service.
+        return null;
+    }
+}