/* Copyright © 2013-2014, Silent Circle, LLC. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Any redistribution, use, or modification is done solely for personal benefit and not for any commercial purpose or for monetary gain * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name Silent Circle nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SILENT CIRCLE, LLC BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * 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.silentcircle.contacts; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.database.Cursor; import android.graphics.Rect; import android.net.Uri; import android.telephony.PhoneNumberUtils; import android.text.TextUtils; import android.view.View; import android.widget.TextView; import com.silentcircle.contacts.model.account.AccountType; import com.silentcircle.contacts.R; import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.Im; import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.Phone; import com.silentcircle.silentcontacts.ScContactsContract.DisplayPhoto; import com.silentcircle.contacts.utils.Constants; import com.silentcircle.contacts.utils.PhoneNumberHelper; public class ContactsUtils { private static final String TAG = "ContactsUtils"; private static final String WAIT_SYMBOL_AS_STRING = String.valueOf(PhoneNumberUtils.WAIT); private static String SILENT_CALL_ACTION = "com.silentcircle.silentphone.action.NEW_OUTGOING_CALL"; private static String SILENT_EDIT_BEFORE_CALL_ACTION = "com.silentcircle.silentphone.action.EDIT_BEFORE_CALL"; private static int sThumbnailSize = -1; // TODO find a proper place for the canonical version of these public interface ProviderNames { String SILENT = "SilentCircle"; String YAHOO = "Yahoo"; String GTALK = "GTalk"; String MSN = "MSN"; String ICQ = "ICQ"; String AIM = "AIM"; String XMPP = "XMPP"; String JABBER = "JABBER"; String SKYPE = "SKYPE"; String QQ = "QQ"; } /** * This looks up the provider name defined in * ProviderNames from the predefined IM protocol id. * This is used for interacting with the IM application. * * @param protocol the protocol ID * @return the provider name the IM app uses for the given protocol, or null if no * provider is defined for the given protocol * @hide */ public static String lookupProviderNameFromId(int protocol) { switch (protocol) { case Im.PROTOCOL_SILENT: return ProviderNames.SILENT; case Im.PROTOCOL_GOOGLE_TALK: return ProviderNames.GTALK; case Im.PROTOCOL_AIM: return ProviderNames.AIM; case Im.PROTOCOL_MSN: return ProviderNames.MSN; case Im.PROTOCOL_YAHOO: return ProviderNames.YAHOO; case Im.PROTOCOL_ICQ: return ProviderNames.ICQ; case Im.PROTOCOL_JABBER: return ProviderNames.JABBER; case Im.PROTOCOL_SKYPE: return ProviderNames.SKYPE; case Im.PROTOCOL_QQ: return ProviderNames.QQ; } return null; } /** * Test if the given {@link CharSequence} contains any graphic characters, * first checking {@link TextUtils#isEmpty(CharSequence)} to handle null. */ public static boolean isGraphic(CharSequence str) { return !TextUtils.isEmpty(str) && TextUtils.isGraphic(str); } /** * Returns true if two objects are considered equal. Two null references are equal here. */ public static boolean areObjectsEqual(Object a, Object b) { return a == b || (a != null && a.equals(b)); } /** * Returns true if two data with mimetypes which represent values in contact entries are * considered equal for collapsing in the GUI. For caller-id, use * {@link PhoneNumberUtils#compare(Context, String, String)} instead */ public static final boolean shouldCollapse(CharSequence mimetype1, CharSequence data1, CharSequence mimetype2, CharSequence data2) { // different mimetypes? don't collapse if (!TextUtils.equals(mimetype1, mimetype2)) return false; // exact same string? good, bail out early if (TextUtils.equals(data1, data2)) return true; // so if either is null, these two must be different if (data1 == null || data2 == null) return false; // if this is not about phone numbers, we know this is not a match (of course, some // mimetypes could have more sophisticated matching is the future, e.g. addresses) if (!TextUtils.equals(Phone.CONTENT_ITEM_TYPE, mimetype1)) return false; return shouldCollapsePhoneNumbers(data1.toString(), data2.toString()); } private static final boolean shouldCollapsePhoneNumbers( String number1WithLetters, String number2WithLetters) { final String number1 = PhoneNumberUtils.convertKeypadLettersToDigits(number1WithLetters); final String number2 = PhoneNumberUtils.convertKeypadLettersToDigits(number2WithLetters); int index1 = 0; int index2 = 0; for (;;) { // Skip formatting characters. while (index1 < number1.length() && !PhoneNumberUtils.isNonSeparator(number1.charAt(index1))) { index1++; } while (index2 < number2.length() && !PhoneNumberUtils.isNonSeparator(number2.charAt(index2))) { index2++; } // If both have finished, match. If only one has finished, not match. final boolean number1End = (index1 == number1.length()); final boolean number2End = (index2 == number2.length()); if (number1End) { return number2End; } if (number2End) return false; // If the non-formatting characters are different, not match. if (number1.charAt(index1) != number2.charAt(index2)) return false; // Go to the next characters. index1++; index2++; } } /** * Returns true if two {@link Intent}s are both null, or have the same action. */ public static final boolean areIntentActionEqual(Intent a, Intent b) { if (a == b) { return true; } if (a == null || b == null) { return false; } return TextUtils.equals(a.getAction(), b.getAction()); } /** * @return The ISO 3166-1 two letters country code of the country the user * is in. */ public static final String getCurrentCountryIso(Context context) { String locale = context.getResources().getConfiguration().locale.getCountry(); return locale; } // TODO public static boolean areContactWritableAccountsAvailable(Context context) { // final List<AccountWithDataSet> accounts = // AccountTypeManager.getInstance(context).getAccounts(true /* writeable */); // return !accounts.isEmpty(); // } // // public static boolean areGroupWritableAccountsAvailable(Context context) { // final List<AccountWithDataSet> accounts = // AccountTypeManager.getInstance(context).getGroupWritableAccounts(); // return !accounts.isEmpty(); // } /** * Returns the intent to launch for the given invitable account type and contact lookup URI. * This will return null if the account type is not invitable (i.e. there is no * {@link com.silentcircle.contacts.model.account.AccountType#getInviteContactActivityClassName()} or * {@link com.silentcircle.contacts.model.account.AccountType#syncAdapterPackageName}). */ public static Intent getInvitableIntent(AccountType accountType, Uri lookupUri) { // String syncAdapterPackageName = accountType.syncAdapterPackageName; // String className = accountType.getInviteContactActivityClassName(); // if (TextUtils.isEmpty(syncAdapterPackageName) || TextUtils.isEmpty(className)) { // return null; // } // Intent intent = new Intent(); // intent.setClassName(syncAdapterPackageName, className); // // intent.setAction(ScContactsContract.Intents.INVITE_CONTACT); // // // Data is the lookup URI. // intent.setData(lookupUri); // return intent; return null; } /** * Return Uri with an appropriate scheme, accepting Voicemail, SIP, and usual phone call * numbers. */ public static Uri getCallUri(String number) { if (PhoneNumberHelper.isUriNumber(number)) { return Uri.fromParts(Constants.SCHEME_SIP, number, null); } return Uri.fromParts(Constants.SCHEME_TEL, number, null); } /** * Get the EDIT_BEFORE_CALL action string. * * @return the correct action string. */ public static String getEditBeforeCallAction() { return SILENT_EDIT_BEFORE_CALL_ACTION; } /** * Return an Intent for making a phone call. Scheme (e.g. tel, sip) will be determined * automatically. */ public static Intent getCallIntent(String number) { return getCallIntent(number, null); } /** * Return an Intent for making a phone call. A given Uri will be used as is (without any * sanity check). */ public static Intent getCallIntent(Uri uri) { return getCallIntent(uri, null); } /** * A variant of {@link #getCallIntent(String)} but also accept a call origin. For more * information about call origin, see comments in Phone package (PhoneApp). */ public static Intent getCallIntent(String number, String callOrigin) { return getCallIntent(getCallUri(number), callOrigin); } /** * A variant of {@link #getCallIntent(Uri)} but also accept a call origin. For more * information about call origin, see comments in Phone package (PhoneApp). */ public static Intent getCallIntent(Uri uri, String callOrigin) { final Intent intent = new Intent(SILENT_CALL_ACTION, uri); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); // if (callOrigin != null) { // intent.putExtra(DialtactsActivity.EXTRA_CALL_ORIGIN, callOrigin); // } return intent; } /** * Returns a header view based on the R.layout.list_separator, where the * containing {@link TextView} is set using the given textResourceId. */ public static View createHeaderView(Context context, int textResourceId) { View view = View.inflate(context, R.layout.list_separator, null); TextView textView = (TextView) view.findViewById(R.id.title); textView.setText(context.getString(textResourceId)); return view; } /** * Returns the {@link Rect} with left, top, right, and bottom coordinates * that are equivalent to the given {@link View}'s bounds. This is equivalent to how the * target {@link Rect} is calculated in {@link QuickContact#showQuickContact}. */ public static Rect getTargetRectFromView(Context context, View view) { final float appScale = 1.0f; // context.getResources().getCompatibilityInfo().applicationScale; final int[] pos = new int[2]; view.getLocationOnScreen(pos); final Rect rect = new Rect(); rect.left = (int) (pos[0] * appScale + 0.5f); rect.top = (int) (pos[1] * appScale + 0.5f); rect.right = (int) ((pos[0] + view.getWidth()) * appScale + 0.5f); rect.bottom = (int) ((pos[1] + view.getHeight()) * appScale + 0.5f); return rect; } /** * Returns the size (width and height) of thumbnail pictures as configured in the provider. This * can safely be called from the UI thread, as the provider can serve this without performing * a database access */ public static int getThumbnailSize(Context context) { if (sThumbnailSize == -1) { final Cursor c = context.getContentResolver().query( DisplayPhoto.CONTENT_MAX_DIMENSIONS_URI, new String[] { DisplayPhoto.THUMBNAIL_MAX_DIM }, null, null, null); try { c.moveToFirst(); sThumbnailSize = c.getInt(0); } finally { c.close(); } } return sThumbnailSize; } /** * @return if the context is in landscape orientation. */ public static boolean isLandscape(Context context) { return context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; } }