Android: не удается запросить ContactsContract.Directory в HTC
Я написал пример приложения, которое возвращает данные о контактах из GAL (каталог компании, определяемый как Exchange ActiveSync в "Учетных записях"). Это же приложение прекрасно работает на множестве разных устройств, но не на HTC, с которым я тестировал (One X+ с Android 4.1.1 и One VX с 4.0.4).
По сути, запрос к ContactsContract.Directory.CONTENT_URI возвращает только каталоги с идентификаторами Directory.DEFAULT и Directory.LOCAL_INVISIBLE. На других устройствах это добавляет, например, id=5 с типом com.android.exchange. Поиск по нативному приложению "Люди" также работает на HTC.
Это полный список кодов двух классов
Основная деятельность
package com.example.galcontactssearch;
import com.example.galcontactssearch.CursorQueryWrapper.CursorResultIterator;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Directory;
import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBarActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.HashSet;
public class MainActivity extends ActionBarActivity {
public static final String TAG = "SEARCH_IN_GAL";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment())
.commit();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* A placeholder fragment containing a simple view.
*/
public static class PlaceholderFragment extends Fragment {
private ArrayList<Long> dirIds;
private String[] projection;
public PlaceholderFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
final Context context = getActivity();
String app_ver = null;
try
{
app_ver =context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
}
catch (NameNotFoundException e)
{
Log.e(TAG, "Cannot find version name", e);
}
Button searchBtn = (Button) rootView.findViewById(R.id.button1);
if(app_ver!=null){
searchBtn.setText(searchBtn.getText() + " v." + app_ver);
}
final EditText text = (EditText) rootView.findViewById(R.id.editText1);
final TextView resultsView = (TextView) rootView.findViewById(R.id.resultsView);
searchBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
final String searchStr = text.getText().toString();
new Thread(new Runnable() {
@Override
public void run() {
final String result;
if (TextUtils.isEmpty(searchStr)) {
result = "No input";
} else {
result = search(searchStr);
}
resultsView.post(new Runnable() {
@Override
public void run() {
resultsView.setText(result);
}
});
}
}).start();
}
});
CursorQueryWrapper wrapper = new CursorQueryWrapper(TAG);
dirIds = new ArrayList<Long>();
wrapper.query(getActivity(), ContactsContract.Directory.CONTENT_URI, new String[] {
Directory._ID, Directory.ACCOUNT_NAME, Directory.ACCOUNT_TYPE
}, null, null, null,
new CursorResultIterator() {
@Override
public void iterate(Cursor cursor) throws Exception {
long id = cursor.getLong(0);
if (Directory.DEFAULT != id && Directory.LOCAL_INVISIBLE != id) {
dirIds.add(id);
Log.d(TAG, "Account: id="+id + " name="+cursor.getString(1) +" type="+cursor.getString(2));
}
}
});
if (dirIds.isEmpty()) {
String result = "Cannot find additional accounts";
Log.e(TAG, result);
searchBtn.setEnabled(false);
resultsView.setText(result);
}else{
HashSet<String> projSet = new HashSet<String>();
projSet.add(StructuredName.DISPLAY_NAME);
projSet.add(StructuredName.GIVEN_NAME);
projSet.add(StructuredName.FAMILY_NAME);
projSet.add(Email.ADDRESS);
projSet.add(Phone.NUMBER);
projSet.add(Contacts.Data.MIMETYPE);
projSet.add(Email.TYPE);
projSet.add(Phone.TYPE);
projection = projSet.toArray(new String[projSet.size()]);
}
return rootView;
}
private String search(final String searchString) {
final StringBuilder bld = new StringBuilder();
CursorQueryWrapper wrapper = new CursorQueryWrapper(TAG);
final Context context = getActivity();
for (Long id : dirIds) {
final String idStr = String.valueOf(id);
Uri uri = ContactsContract.Contacts.CONTENT_FILTER_URI.buildUpon().appendEncodedPath(searchString)
.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY, idStr).build();
final ArrayList<String> lookupKeys = new ArrayList<String>();
wrapper.query(context, uri, new String[] {
Contacts.LOOKUP_KEY
}, null, null, null, new CursorResultIterator() {
@Override
public void iterate(Cursor cursor) throws Exception {
lookupKeys.add(cursor.getString(0));
}
});
if (lookupKeys.isEmpty()) {
continue;
}
for (String lookupKey : lookupKeys) {
uri = ContactsContract.Contacts.CONTENT_LOOKUP_URI.buildUpon().appendEncodedPath(lookupKey)
.appendPath(Contacts.Entity.CONTENT_DIRECTORY)
.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY, idStr).build();
wrapper.query(context, uri, projection, null, null, null, new CursorResultIterator() {
@Override
public void iterate(Cursor cursor) throws Exception {
String mime = cursor.getString(cursor.getColumnIndex(Contacts.Data.MIMETYPE));
if (StructuredName.CONTENT_ITEM_TYPE.equals(mime)) {
String result = "Given name:"
+ cursor.getString(cursor.getColumnIndex(StructuredName.GIVEN_NAME))
+ " Family name:"
+ cursor.getString(cursor.getColumnIndex(StructuredName.FAMILY_NAME))
+ " Display name:"
+ cursor.getString(cursor.getColumnIndex(StructuredName.DISPLAY_NAME));
Log.d(TAG, result);
bld.append(result).append('\n');
} else if (Email.CONTENT_ITEM_TYPE.equals(mime)) {
String result = "Email address:"
+ cursor.getString(cursor.getColumnIndex(Email.ADDRESS))
+ " with type:"
+ Email.getTypeLabel(context.getResources(), cursor.getInt(cursor
.getColumnIndex(Email.TYPE)), null);
Log.d(TAG, result);
bld.append(result).append('\n');
}
else if (Phone.CONTENT_ITEM_TYPE.equals(mime)) {
String result = "Phone num:"
+ cursor.getString(cursor.getColumnIndex(Phone.NUMBER))
+ " with type:"
+ Phone.getTypeLabel(context.getResources(), cursor.getInt(cursor
.getColumnIndex(Phone.TYPE)), null);
Log.d(TAG, result);
bld.append(result).append('\n');
}
}
});
String result = "*************************************";
Log.d(TAG, result);
bld.append(result).append('\n');
}
}
if (bld.length() == 0) {
String result = "Cannot find matching contacts for the string '" + searchString + "'";
Log.d(TAG, result);
return result;
}
return bld.toString();
}
}
}
CursorQueryWrapper
package com.example.galcontactssearch;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
public class CursorQueryWrapper {
private final String tag;
private QueryInterface queryInterface;
public static class CursorIteratonInterrupt extends Exception {
private static final long serialVersionUID = -1124533346986767621L;
}
public static abstract class CursorResultIterator {
public void iterate(Cursor cursor) throws Exception {
}
public boolean shouldIterate() {
return true;
}
public void prepareForIterations(Cursor cursor) {
}
}
public interface QueryInterface {
Cursor query(Context context, Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder);
}
public static class ContextQuery implements QueryInterface {
@Override
public Cursor query(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
return context.getContentResolver()
.query(uri, projection, selection, selectionArgs, sortOrder);
}
}
public CursorQueryWrapper(final String tag) {
this(tag, null);
}
public CursorQueryWrapper(final String tag, QueryInterface queryInterface) {
this.tag = tag;
this.queryInterface = queryInterface == null ? new ContextQuery() : queryInterface;
}
public boolean query(Context context, Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder, CursorResultIterator iterator) {
Cursor cursor = null;
Log.d(tag, "Starting query");
try {
StringBuilder bld = new StringBuilder(
"Running query");
bld.append("\nUri: ").append(uri.toString());
bld.append("\nProjection: ").append(projection == null ? "null" : TextUtils.join(", ", projection));
bld.append("\nSelection: ").append(selection);
bld.append("\nSelectionArgs: ").append(
selectionArgs == null ? "null" : TextUtils.join(", ", selectionArgs));
bld.append("\nSortOrder: ").append(sortOrder);
Log.d(tag, bld.toString());
cursor = queryInterface.query(context, uri, projection, selection, selectionArgs, sortOrder);
if (cursor == null) {
Log.e(tag, "Cannot process the query. The cursor is null as a result of some error.");
return false;
}
Log.d(tag, "Cursor's count is " + cursor.getCount());
iterator.prepareForIterations(cursor);
if (iterator.shouldIterate()) {
while (cursor.moveToNext()) {
iterator.iterate(cursor);
}
}
} catch (Exception e) {
if (!(e instanceof CursorIteratonInterrupt)) {
Log.e(tag,
"Cannot process the query.", e);
return false;
}
} finally {
if (cursor != null) {
cursor.close();
}
}
Log.d(tag, "The query is ended successfully");
return true;
}
}
Кроме того, есть это приложение, "Настоящие контакты", которое, по сути, является просто самым последним приложением ICS для контактов, скомпилированным. Например, он ищет GAL на Samsung Galaxy S3, но не на HTC. Может кто-нибудь пролить свет на эту проблему, пожалуйста?