Adding classes that were part of com.android.phone.common.

Bug: 7054788
Change-Id: If26c76fc79d5b8cba543d69869c48faca9614e2b
diff --git a/Android.mk b/Android.mk
index 62cf6fc..f01263e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -14,12 +14,10 @@
 
 LOCAL_PATH:= $(call my-dir)
 
-# Static library with some common classes for the phone apps.
-# To use it add this line in your Android.mk
-#  LOCAL_STATIC_JAVA_LIBRARIES := com.android.phone.common
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_MODULE := com.android.phone.shared
+
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/src/com/android/phone/common/CallLogAsync.java b/src/com/android/phone/common/CallLogAsync.java
new file mode 100644
index 0000000..e8ed6b6
--- /dev/null
+++ b/src/com/android/phone/common/CallLogAsync.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2010 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.phone.common;
+import android.content.Context;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Looper;
+import android.provider.CallLog.Calls;
+import android.util.Log;
+import com.android.internal.telephony.CallerInfo;
+
+/**
+ * Class to access the call logs database asynchronously since
+ * database ops can take a long time depending on the system's load.
+ * It uses AsyncTask which has its own thread pool.
+ *
+ * <pre class="prettyprint">
+ * Typical usage:
+ * ==============
+ *
+ *  // From an activity...
+ *  String mLastNumber = "";
+ *
+ *  CallLogAsync log = new CallLogAsync();
+ *
+ *  CallLogAsync.AddCallArgs addCallArgs = new CallLogAsync.AddCallArgs(
+ *      this, ci, number, presentation, type, timestamp, duration);
+ *
+ *  log.addCall(addCallArgs);
+ *
+ *  CallLogAsync.GetLastOutgoingCallArgs lastCallArgs = new CallLogAsync.GetLastOutgoingCallArgs(
+ *      this, new CallLogAsync.OnLastOutgoingCallComplete() {
+ *               public void lastOutgoingCall(String number) { mLastNumber = number; }
+ *            });
+ *  log.getLastOutgoingCall(lastCallArgs);
+ * </pre>
+ *
+ */
+
+public class CallLogAsync {
+    private static final String TAG = "CallLogAsync";
+
+    /**
+     * Parameter object to hold the args to add a call in the call log DB.
+     */
+    public static class AddCallArgs {
+        /**
+         * @param ci               CallerInfo.
+         * @param number           To be logged.
+         * @param presentation     Of the number.
+         * @param callType         The type of call (e.g INCOMING_TYPE). @see
+         *                         android.provider.CallLog for the list of values.
+         * @param timestamp        Of the call (millisecond since epoch).
+         * @param durationInMillis Of the call (millisecond).
+         */
+        public AddCallArgs(Context context,
+                           CallerInfo ci,
+                           String number,
+                           int presentation,
+                           int callType,
+                           long timestamp,
+                           long durationInMillis) {
+            // Note that the context is passed each time. We could
+            // have stored it in a member but we've run into a bunch
+            // of memory leaks in the past that resulted from storing
+            // references to contexts in places that were long lived
+            // when the contexts were expected to be short lived. For
+            // example, if you initialize this class with an Activity
+            // instead of an Application the Activity can't be GCed
+            // until this class can, and Activities tend to hold
+            // references to large amounts of RAM for things like the
+            // bitmaps in their views.
+            //
+            // Having hit more than a few of those bugs in the past
+            // we've grown cautious of storing references to Contexts
+            // when it's not very clear that the thing holding the
+            // references is tightly tied to the Context, for example
+            // Views the Activity is displaying.
+
+            this.context = context;
+            this.ci = ci;
+            this.number = number;
+            this.presentation = presentation;
+            this.callType = callType;
+            this.timestamp = timestamp;
+            this.durationInSec = (int)(durationInMillis / 1000);
+        }
+        // Since the members are accessed directly, we don't use the
+        // mXxxx notation.
+        public final Context context;
+        public final CallerInfo ci;
+        public final String number;
+        public final int presentation;
+        public final int callType;
+        public final long timestamp;
+        public final int durationInSec;
+    }
+
+    /**
+     * Parameter object to hold the args to get the last outgoing call
+     * from the call log DB.
+     */
+    public static class GetLastOutgoingCallArgs {
+        public GetLastOutgoingCallArgs(Context context,
+                                       OnLastOutgoingCallComplete callback) {
+            this.context = context;
+            this.callback = callback;
+        }
+        public final Context context;
+        public final OnLastOutgoingCallComplete callback;
+    }
+
+    /**
+     * Non blocking version of CallLog.addCall(...)
+     */
+    public AsyncTask addCall(AddCallArgs args) {
+        assertUiThread();
+        return new AddCallTask().execute(args);
+    }
+
+    /** Interface to retrieve the last dialed number asynchronously. */
+    public interface OnLastOutgoingCallComplete {
+        /** @param number The last dialed number or an empty string if
+         *                none exists yet. */
+        void lastOutgoingCall(String number);
+    }
+
+    /**
+     * CallLog.getLastOutgoingCall(...)
+     */
+    public AsyncTask getLastOutgoingCall(GetLastOutgoingCallArgs args) {
+        assertUiThread();
+        return new GetLastOutgoingCallTask(args.callback).execute(args);
+    }
+
+    /**
+     * AsyncTask to save calls in the DB.
+     */
+    private class AddCallTask extends AsyncTask<AddCallArgs, Void, Uri[]> {
+        @Override
+        protected Uri[] doInBackground(AddCallArgs... callList) {
+            int count = callList.length;
+            Uri[] result = new Uri[count];
+            for (int i = 0; i < count; i++) {
+                AddCallArgs c = callList[i];
+
+                try {
+                    // May block.
+                    result[i] = Calls.addCall(
+                            c.ci, c.context, c.number, c.presentation,
+                            c.callType, c.timestamp, c.durationInSec);
+                } catch (Exception e) {
+                    // This must be very rare but may happen in legitimate cases.
+                    // e.g. If the phone is encrypted and thus write request fails, it may
+                    // cause some kind of Exception (right now it is IllegalArgumentException, but
+                    // might change).
+                    //
+                    // We don't want to crash the whole process just because of that.
+                    // Let's just ignore it and leave logs instead.
+                    Log.e(TAG, "Exception raised during adding CallLog entry: " + e);
+                    result[i] = null;
+                }
+            }
+            return result;
+        }
+
+        // Perform a simple sanity check to make sure the call was
+        // written in the database. Typically there is only one result
+        // per call so it is easy to identify which one failed.
+        @Override
+        protected void onPostExecute(Uri[] result) {
+            for (Uri uri : result) {
+                if (uri == null) {
+                    Log.e(TAG, "Failed to write call to the log.");
+                }
+            }
+        }
+    }
+
+    /**
+     * AsyncTask to get the last outgoing call from the DB.
+     */
+    private class GetLastOutgoingCallTask extends AsyncTask<GetLastOutgoingCallArgs, Void, String> {
+        private final OnLastOutgoingCallComplete mCallback;
+        private String mNumber;
+        public GetLastOutgoingCallTask(OnLastOutgoingCallComplete callback) {
+            mCallback = callback;
+        }
+
+        // Happens on a background thread. We cannot run the callback
+        // here because only the UI thread can modify the view
+        // hierarchy (e.g enable/disable the dial button). The
+        // callback is ran rom the post execute method.
+        @Override
+        protected String doInBackground(GetLastOutgoingCallArgs... list) {
+            int count = list.length;
+            String number = "";
+            for (GetLastOutgoingCallArgs args : list) {
+                // May block. Select only the last one.
+                number = Calls.getLastOutgoingCall(args.context);
+            }
+            return number;  // passed to the onPostExecute method.
+        }
+
+        // Happens on the UI thread, it is safe to run the callback
+        // that may do some work on the views.
+        @Override
+        protected void onPostExecute(String number) {
+            assertUiThread();
+            mCallback.lastOutgoingCall(number);
+        }
+    }
+
+    private void assertUiThread() {
+        if (!Looper.getMainLooper().equals(Looper.myLooper())) {
+            throw new RuntimeException("Not on the UI thread!");
+        }
+    }
+}
diff --git a/src/com/android/phone/common/HapticFeedback.java b/src/com/android/phone/common/HapticFeedback.java
new file mode 100644
index 0000000..c470730
--- /dev/null
+++ b/src/com/android/phone/common/HapticFeedback.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2009 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.phone.common;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.SystemVibrator;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.provider.Settings.System;
+import android.util.Log;
+
+/**
+ * Handles the haptic feedback: a light buzz happening when the user
+ * presses a soft key (UI button or capacitive key).  The haptic
+ * feedback is controlled by:
+ * - a system resource for the pattern
+ *   The pattern used is tuned per device and stored in an internal
+ *   resource (config_virtualKeyVibePattern.)
+ * - a system setting HAPTIC_FEEDBACK_ENABLED.
+ *   HAPTIC_FEEDBACK_ENABLED can be changed by the user using the
+ *   system Settings activity. It must be rechecked each time the
+ *   activity comes in the foreground (onResume).
+ *
+ * This class is not thread safe. It assumes it'll be called from the
+ * UI thead.
+ *
+ * Typical usage:
+ * --------------
+ *   static private final boolean HAPTIC_ENABLED = true;
+ *   private HapticFeedback mHaptic = new HapticFeedback();
+ *
+ *   protected void onCreate(Bundle icicle) {
+ *     mHaptic.init((Context)this, HAPTIC_ENABLED);
+ *   }
+ *
+ *   protected void onResume() {
+ *     // Refresh the system setting.
+ *     mHaptic.checkSystemSetting();
+ *   }
+ *
+ *   public void foo() {
+ *     mHaptic.vibrate();
+ *   }
+ *
+ */
+
+public class HapticFeedback {
+    private static final int VIBRATION_PATTERN_ID =
+            com.android.internal.R.array.config_virtualKeyVibePattern;
+    /** If no pattern was found, vibrate for a small amount of time. */
+    private static final long DURATION = 10;  // millisec.
+    /** Play the haptic pattern only once. */
+    private static final int NO_REPEAT = -1;
+
+    private static final String TAG = "HapticFeedback";
+    private Context mContext;
+    private long[] mHapticPattern;
+    private Vibrator mVibrator;
+
+    private boolean mEnabled;
+    private Settings.System mSystemSettings;
+    private ContentResolver mContentResolver;
+    private boolean mSettingEnabled;
+
+    /**
+     * Initialize this instance using the app and system
+     * configs. Since these don't change, init is typically called
+     * once in 'onCreate'.
+     * checkSettings is not called during init.
+     * @param context To look up the resources and system settings.
+     * @param enabled If false, vibrate will be a no-op regardless of
+     * the system settings.
+     */
+    public void init(Context context, boolean enabled) {
+        mEnabled = enabled;
+        if (enabled) {
+            // We don't rely on getSystemService(Context.VIBRATOR_SERVICE) to make sure this
+            // vibrator object will be isolated from others.
+            mVibrator = new SystemVibrator();
+            if (!loadHapticSystemPattern(context.getResources())) {
+                mHapticPattern = new long[] {0, DURATION, 2 * DURATION, 3 * DURATION};
+            }
+            mSystemSettings = new Settings.System();
+            mContentResolver = context.getContentResolver();
+        }
+    }
+
+
+    /**
+     * Reload the system settings to check if the user enabled the
+     * haptic feedback.
+     */
+    public void checkSystemSetting() {
+        if (!mEnabled) {
+            return;
+        }
+        try {
+            int val = mSystemSettings.getInt(mContentResolver, System.HAPTIC_FEEDBACK_ENABLED, 0);
+            mSettingEnabled = val != 0;
+        } catch (Resources.NotFoundException nfe) {
+            Log.e(TAG, "Could not retrieve system setting.", nfe);
+            mSettingEnabled = false;
+        }
+
+    }
+
+
+    /**
+     * Generate the haptic feedback vibration. Only one thread can
+     * request it. If the phone is already in a middle of an haptic
+     * feedback sequence, the request is ignored.
+     */
+    public void vibrate() {
+        if (!mEnabled || !mSettingEnabled) {
+            return;
+        }
+        // System-wide configuration may return different styles of haptic feedback pattern.
+        // - an array with one value implies "one-shot vibration"
+        // - an array with multiple values implies "pattern vibration"
+        // We need to switch methods to call depending on the difference.
+        // See also PhoneWindowManager#performHapticFeedbackLw() for another example.
+        if (mHapticPattern != null && mHapticPattern.length == 1) {
+            mVibrator.vibrate(mHapticPattern[0]);
+        } else {
+            mVibrator.vibrate(mHapticPattern, NO_REPEAT);
+        }
+    }
+
+    /**
+     * @return true If the system haptic pattern was found.
+     */
+    private boolean loadHapticSystemPattern(Resources r) {
+        int[] pattern;
+
+        mHapticPattern = null;
+        try {
+            pattern = r.getIntArray(VIBRATION_PATTERN_ID);
+        } catch (Resources.NotFoundException nfe) {
+            Log.e(TAG, "Vibrate pattern missing.", nfe);
+            return false;
+        }
+
+        if (null == pattern || pattern.length == 0) {
+            Log.e(TAG, "Haptic pattern is null or empty.");
+            return false;
+        }
+
+        // int[] to long[] conversion.
+        mHapticPattern = new long[pattern.length];
+        for (int i = 0; i < pattern.length; i++) {
+            mHapticPattern[i] = pattern[i];
+        }
+        return true;
+    }
+}