グループ名も同時に読み込む

ContentResolver で、電話帳の Email、氏名、所属グループの名称、全てを一度に取得しようと思っても、
CommonDataKinds.GroupMembership と、ContactsContract.Data.CONTENT_URI
、CommonDataKinds.Email.CONTENT_URI 
 で、情報を取得、CommonDataKinds.GroupMembership.GROUP_ROW_ID
を取得しても、ContactsContract.Groups.CONTENT_URI
との結合がうまくいかない。
CursorJoiner は、文字列比較なので、GROUP_ROW_ID での結合が失敗してしまう。

諦めて、ContactsContract.Groups.CONTENT_URI だけでグループ名だけは、先に取得して、
ContactsContract.Data.CONTENT_URI で全部読みながら結合していくしかないみたいなので、
原始的なロジックになってもやってみる。
・Contact_ID でソートをすること。
・MIMETYPEを判定して、名前情報なのか、Emailなのか、GroupMembership の取得なのかを判定する。


public abstract class AllReadBook{
   protected abstract void onRead(int contactId, String displayName, String familyName, String givenName
         , String phoneticName, String phoneticFamilyName, String phonticeGivenName, boolean starred
         , String emails, int groupIds, String titles);

   public void scan(Context context){
      // Group 読み出し
      HashMap<Integer,String> titleMap = new HashMap<Integer,String>();
      Cursor curgrp = context.getContentResolver().query(ContactsContract.Groups.CONTENT_URI
         , new String
{ BaseColumns._ID, ContactsContract.Groups.TITLE, ContactsContract.Groups.GROUP_VISIBLE, ContactsContract.Groups.DELETED  }
         , ContactsContract.Groups.GROUP_VISIBLE +"==1 AND " + ContactsContract.Groups.DELETED + "==0", null
         , null);
      if (curgrp.moveToFirst()){
         do{
            titleMap.put(curgrp.getInt(0), curgrp.getString(1));
         }while(curgrp.moveToNext());
      }
      curgrp.close();

      // 読込み保持用
      String displayName = null;
      String email = null;
      Integer groupRowId = null;
      String familyName = null;
      String givenName = null;
      String phoneticName = null;
      String phoneticFamilyName = null;
      String phoneticGivenName = null;
      boolean starred = false;
      List<String> mailList = new ArrayList<String>();
      List<Integer> groupIdList = new ArrayList<Integer>();
      List<String> groupNameList = new ArrayList<String>();
      // 全て読込み
      Uri uri = ContactsContract.Data.CONTENT_URI;
      String projection = { ContactsContract.Data.CONTACT_ID
            , ContactsContract.Data.MIMETYPE
            , ContactsContract.Data.DATA1
            , CommonDataKinds.StructuredName.FAMILY_NAME
            , CommonDataKinds.StructuredName.GIVEN_NAME
            , CommonDataKinds.StructuredName.PHONETIC_NAME
            , CommonDataKinds.StructuredName.PHONETIC_FAMILY_NAME
            , CommonDataKinds.StructuredName.PHONETIC_GIVEN_NAME
            , ContactsContract.RawContacts.STARRED };
      String selection = ContactsContract.Data.MIMETYPE  + " =? OR " + ContactsContract.Data.MIMETYPE
      + " =? OR "+ ContactsContract.Data.MIMETYPE + " =? ";
      String
 selectionArgs = { StructuredName.CONTENT_ITEM_TYPE
                                 , CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE
                                 , CommonDataKinds.Email.CONTENT_ITEM_TYPE
      };
      String sortOrder = RawContacts.CONTACT_ID + " ASC";
      Cursor cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);

      if (cursor.moveToFirst()){
         Integer id = cursor.getInt(0);
         String mimetype = cursor.getString(1);
         if (StructuredName.CONTENT_ITEM_TYPE.equals(mimetype)){
            displayName = cursor.getString(2);
            familyName = cursor.getString(3);
            givenName = cursor.getString(4);
            phoneticName = cursor.getString(5);
            phoneticFamilyName = cursor.getString(6);
            phoneticGivenName = cursor.getString(7);
            starred  = cursor.getInt(8)==1;
         }else if(CommonDataKinds.Email.CONTENT_ITEM_TYPE.equals(mimetype)){
            email = cursor.getString(2);
            mailList.add(email);
         }else if(CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE.equals(mimetype)){
            groupRowId = cursor.getInt(2);
            groupIdList.add(groupRowId);
            groupNameList.add(titleMap.get(groupRowId));
         }
         Integer contactId = null;
         while(cursor.moveToNext()){
            mimetype = cursor.getString(1);
            if (StructuredName.CONTENT_ITEM_TYPE.equals(mimetype)){
               displayName = cursor.getString(2);
               familyName = cursor.getString(3);
               givenName = cursor.getString(4);
               phoneticName = cursor.getString(5);
               phoneticFamilyName = cursor.getString(6);
               phoneticGivenName = cursor.getString(7);
               starred  = cursor.getInt(8)==1;
            }else if(CommonDataKinds.Email.CONTENT_ITEM_TYPE.equals(mimetype)){
               email = cursor.getString(2);
               mailList.add(email);
            }else if(CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE.equals(mimetype)){
               groupRowId = cursor.getInt(2);
               groupIdList.add(groupRowId);
               groupNameList.add(titleMap.get(groupRowId));
            }
            contactId = cursor.getInt(0);

            if (!contactId.equals(id)){
               String emails = mailList.toArray(new String{});
               int groupIds = new int[groupIdList.size()];
               int n=0;
               for(int i:groupIdList){
                  groupIds[n] = i;
                  n++;
               }
               String
 titles = groupNameList.toArray(new String{});
               // 抽象メソッド実行
               onRead(id, displayName, familyName, givenName, phoneticName, phoneticFamilyName, phoneticGivenName, starred
                     , emails, groupIds, titles);

               displayName = null;
               email = null;
               groupRowId = null;
               familyName = null;
               givenName = null;
               phoneticName = null;
               phoneticFamilyName = null;
               phoneticGivenName = null;
               starred = false;
               mailList.clear();
               groupIdList.clear();
               groupNameList.clear();
               id = contactId;
            }
         }
         if (contactId != null){
            String
 emails = mailList.toArray(new String{});
            int
 groupIds = new int[groupIdList.size()];
            int n=0;
            for(int i:groupIdList){
               groupIds[n] = i;
               n++;
            }
            String titles = groupNameList.toArray(new String{});
            // 抽象メソッド実行
            onRead(contactId, displayName, familyName, givenName, phoneticName, phoneticFamilyName, phoneticGivenName, starred
                  , emails, groupIds, titles);

         }
      }
      cursor.close();
   }
}

docomo_syncanchor って、何だかよくわからない

先日、Android のコンテンツプロバイダーから電話帳グループ名を取得するサンプルを作っていて
気付いたこと。。。
グループ名が、NULL のものがある。

実験した特のコードは、以下のようなもの。。

ContactsContract.Groups.CONTENT_URI;
String[] projection = {
BaseColumns._ID
, ContactsContract.Groups.TITLE
, ContactsContract.Groups.GROUP_IS_READ_ONLY
, ContactsContract.RawContacts.ACCOUNT_NAME
, ContactsContract.RawContacts.ACCOUNT_TYPE

, ContactsContract.Groups.GROUP_VISIBLE
, ContactsContract.Groups.DELETED
};
String sortOrder = BaseColumns._ID + " ASC";
Cursor cursor = context.getContentResolver().query(uri, projection, null, null, sortOrder);

取得したもの中に、ContactsContract.Groups.TITLE が、NULL のものが存在する。
この行は、 ContactsContract.Groups.DELETED の列も =1 (true) 削除フラグONであり、
削除して、IDだけ残っていることになっている。
迷惑なことに、ContactsContract.Groups.GROUP_VISIBLE は、=1で参照可能な状態、

グループのアカウント名、アカウントタイプ名、
ContactsContract.RawContacts.ACCOUNT_NAME ,  ContactsContract.RawContacts.ACCOUNT_TYPE

これらは、Google であれば、、端末で使用している Google のアカウント名+"@gmail.com" が、ACCOUNT_NAME として取得でき、
ACCOUNT_TYPE は、com.google である。

使用の通信キャリアが、docomo であれば、端末で作ったグループ名がその ACCOUNT_TYPE で、
docomo の場合、、、

ACCOUNT_NAME → docomo
ACCOUNT_TYPE → com.android.nttdocomo

である。
これが、グループ名=NULL・・・削除したグループの行で
取得する ACCOUNT_NAME が、、、

ACCOUNT_NAME = docomo_syncanchor

になっている。→ これが、よくわからず、謎である。。。

DialogFragment の style を変更する

DialogFragment のスタイルを変える。アプリ全体のスタイルとは異なるものを使う場合、
res/values/styles.xml で、Dialog で使う style を追加する。

アプリのテーマが、 Holo で、ダイアログを Holo.Light にする。

<style name="DialogThemeHoloLight" parent="android:style/Theme.Holo.Light.Dialog"></style>


この追加だけで、Dialog に style を適用すると以下のように周りが残ってしまう。

f:id:posturan:20160313192439p:plain


windowBackground で透明を指定しないといけない。


<style name="DialogThemeHoloLight" parent="android:style/Theme.Holo.Light.Dialog">
    <item name="android:windowBackground">@android:color/transparent</item>
</style>

これで、以下のように表示できる。

f:id:posturan:20160313192431p:plain



DialogFragment で、style の指定は onCreateDialog で行う。

public class SpinnerProgressDialogFragment extends DialogFragment{

   private static ProgressDialog progressDialog;

   @Override
   public Dialog onCreateDialog(Bundle savedInstanceState){

      if (progressDialog != null) return progressDialog;
      progressDialog = new ProgressDialog(getActivity(), R.style.DialogThemeHoloLight);
          :
         省略
          :
      return progressDialog;
   }
   @Override
   public Dialog getDialog(){
      return progressDialog;
   }
   @Override
   public void onDestroy(){
      super.onDestroy();
      progressDialog = null;
   }
}

Fragment を BACKキーで戻すのが失敗したら、

Android Fragment を切り替えた後、BACKキー押して Fragment を戻す場合のやり方は、

FragmentTransaction replace add の後で、addToBackStack を null 指定して実行しておくのだが、

戻らず、Activity が検知して終わってしまうことがある。

stackoverflow に、そこそこの Vote UP があるみたいだが、、、
http://stackoverflow.com/questions/7992216/android-fragment-handle-back-button-press


Activity の onBackPressed において、 FragmentManager の getBackStackEntryCount() で問い合わせて、
0だったら、もう一度、super.onBackPressed()
を実行すれば、きちんと戻って
Fragment がスタックに無いことで終了できるではないですか。

FragmentTransaction tx = getFragmentManager().beginTransaction();
tx.replace(R.id.container, fragment);
tx.addToBackStack(null);
tx.commit();
============
@Override
public void onBackPressed(){
   super.onBackPressed();
   if (0==getFragmentManager().getBackStackEntryCount()){
      super.onBackPressed();
   }
}

SearchView で検索した後、キーボードを閉じる

Androroid SearchView で検索した後、キーボードを閉じるという処理を、書かないとダメだった。
先日、
http://blog.zaq.ne.jp/oboe2uran/article/987/
ここまで、書いたけど実践では以下のようにしないとダメみたいだ。

・SearchView#setOnFocusChangeListener にフォーカスChangeで、キーボードを閉じる処理を書くこと。
・検索後アクションバーの MenuItem を閉じること。→ collapseActionView()の実行


MenuItem menuItem;

@Override
public boolean onCreateOptionsMenu(Menu menu){
   getMenuInflater().inflate(R.menu.search_menu, menu);

   SearchManager searchManager = (SearchManager)getSystemService(Context.SEARCH_SERVICE);
   SearchableInfo searchableInfo = searchManager.getSearchableInfo(getComponentName());
   menuItem = menu.findItem(R.id.action_search);

   SearchView searchView = (SearchView)menuItem.getActionView();
   searchView.setSearchableInfo(searchableInfo);
   searchView.setIconifiedByDefault(false);
   searchView.setImeOptions(EditorInfo.IME_ACTION_SEARCH);
   searchView.setOnFocusChangeListener(new View.OnFocusChangeListener(){
      @Override
      public void onFocusChange(View v, boolean hasFocus){
         // キーボードを閉じる
         InputMethodManager inputMethodManager = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
         inputMethodManager.hideSoftInputFromWindow(v.getWindowToken(), 0);

      }
   });
   return true;
}

@Override
protected void onNewIntent(Intent intent) {

   setIntent(intent);
   if (Intent.ACTION_SEARCH.equals(intent.getAction())==true){
      final String query = intent.getStringExtra(SearchManager.QUERY);
      // 端末内検索を、AsyncTask で実行する
      new SearchTask(this){
         @Override
         protected void founded(Integer count){
            if (count==0){
               Toast.makeText(getApplicationContext(), "見つかりませんでした。", Toast.LENGTH_SHORT).show();
            }else{
               // 見つかった時の処理
               // 見つかったら SearchView閉じる。

               menuItem.collapseActionView();
            }
         }
      }.execute(Environment.getExternalStorageDirectory().getAbsolutePath(), query);
   }
}

DialogFragment に CancelListner

以前、シンプルな使い回しのメッセージを出すだけの DialogFragment を書いた。
http://blog.zaq.ne.jp/oboe2uran/article/875/
この当時は、キャンセル時に、CancelListner を登録しておいて動かそうとは思わなかった。
DialogFragment onCreateDialog で、AlertDialog.Builder の setOnCancelListener
実行すると、例外になってしまう。

IllegalStateException: You can not set Dialog's OnCancelListener or OnDismissListener

よくよく、AndroidAPI ドキュメント、DialogFragment の説明を見ると、、

Note: DialogFragment own the Dialog.setOnCancelListener and Dialog.setOnDismissListener callbacks.
You must not set them yourself. To find out about these events, override onCancel(DialogInterface)
 and onDismiss(DialogInterface).


自分で、onCancel override しなさいと。。。

public class SimpleMessageDialog extends DialogFragment

というのを用意するなら、生成時に

 
DialogFragment = new SimpleMessageDialog(){
   @Override
   public void onCancel(DialogInterface dialog){
      // キャンセル時の処理
   }
};


ということなんだけど、キャンセル時の処理のステップが多いとちょっと嫌だ。
リスナを用意したい。

http://blog.zaq.ne.jp/oboe2uran/article/875/ で書いたものを
書き直すことにした。

=================== SimpleDialogInterface =================================
import java.io.Serializable;
/**
 * SimpleDialogInterface
 */

public interface SimpleDialogInterface extends Serializable{
   public final static String MESSAGE = "Message";
   public final static String TITLE   = "Title";
   public final static String CANCELABLE   = "Cancelable";
   public final static int POSITIVE = 0;
   public final static int NEGATIVE = 1;
   public final static int NEUTRAL  = 2;
   public final static String POSITIVE_LABEL   = "PositiveLabel";
   public final static String NEGATIVE_LABEL   = "NegativeLabel";
   public final static String NEUTRAL_LABEL    = "NeutralLabel";
   public final static String LISTENER         = "Listener";
   public final static String CANCEL_LISTNER = "Cancel_Listener";
   public final static String DISMISSL_LISTNER = "dismiss_Listener";


   /**
    * Dialog Button on Click
    * @param which 各ボタンが押された時の判断として以下の値を返す
    * PositiveButton → 0 = SimpleDialogInterface.POSITIVE
    * NegativeButton → 1 = SimpleDialogInterface.NEGATIVE
    * NeutralButton  → 2 = SimpleDialogInterface.NEUTRAL
    */

   public void onButtonClick(int which);
}

=================== SimpleMessageDialog =================================

import java.io.Serializable;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.os.Bundle;
/**
 * SimpleMessageDialog
 */

public class SimpleMessageDialog extends DialogFragment{

   public interface CancelListener extends Serializable{
      public void canceled(DialogInterface _interface);
   }
   private CancelListener cancelListener = new CancelListener(){
      @Override
      public void canceled(DialogInterface _interface){
      }
   };
   public interface DismisslListener extends Serializable{
      public void dismissed(DialogInterface _interface);
   }
   private DismisslListener dismissListener = new DismisslListener(){
      @Override
      public void dismissed(DialogInterface _interface){
      }
   };


   @Override
   public Dialog onCreateDialog(Bundle savedInstanceState){
      AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
      Bundle bundle = getArguments();
      builder.setTitle(bundle.getString(SimpleDialogInterface.TITLE, null));
      String message = bundle.getString(SimpleDialogInterface.MESSAGE, null);
      if (message != null) builder.setMessage(message);
      setCancelable(getArguments().getBoolean(SimpleDialogInterface.CANCELABLE, true));
      String positiveLabel = bundle.getString(SimpleDialogInterface.POSITIVE_LABEL, null);
      String negativeLabel = bundle.getString(SimpleDialogInterface.NEGATIVE_LABEL, null);
      String neutralLabel = bundle.getString(SimpleDialogInterface.NEUTRAL_LABEL, null);
      if (positiveLabel != null || negativeLabel != null || neutralLabel != null){
         final SimpleDialogInterface simpleface = (SimpleDialogInterface)bundle.getSerializable(SimpleDialogInterface.LISTENER);
         if (positiveLabel != null){
            builder.setPositiveButton(positiveLabel, new DialogInterface.OnClickListener(){
               @Override
               public void onClick(DialogInterface dialog, int which){
                  simpleface.onButtonClick(SimpleDialogInterface.POSITIVE);
               }
            });
         }
         if (negativeLabel != null){
            builder.setNegativeButton(negativeLabel, new DialogInterface.OnClickListener(){
               @Override
               public void onClick(DialogInterface dialog, int which){
                  simpleface.onButtonClick(SimpleDialogInterface.NEGATIVE);
               }
            });
         }
         if (neutralLabel != null){
            builder.setNeutralButton(neutralLabel, new DialogInterface.OnClickListener(){
               @Override
               public void onClick(DialogInterface dialog, int which){
                  simpleface.onButtonClick(SimpleDialogInterface.NEUTRAL);
               }
            });
         }
      }
      if (bundle.containsKey(SimpleDialogInterface.CANCEL_LISTNER)){
         cancelListener = (CancelListener)bundle.getSerializable(SimpleDialogInterface.CANCEL_LISTNER);
      }
      if (bundle.containsKey(SimpleDialogInterface.DISMISSL_LISTNER)){
         dismissListener = (DismisslListener)bundle.getSerializable(SimpleDialogInterface.DISMISSL_LISTNER);
      }

      return builder.create();
   }
   @Override
   public void onCancel(DialogInterface _dialog){
      super.onCancel(_dialog);
      cancelListener.canceled(_dialog);
   }
   @Override
   public void onDismiss(DialogInterface dialog){
      super.onDismiss(dialog);
      dismissListener.dismissed(dialog);
   }

}

電話帳、Contact ContentResolver 使い方サンプル

電話帳、Contact の情報は、ContentResolverで読む。

Android 端末 使用中SIMの登録したGoogle アカウント上の Contact 情報、まで読込む場合、

ContactsContract.CommonDataKinds.Email.CONTENT_URI を使う。

public void scan(Context context){
   Uri uri = ContactsContract.CommonDataKinds.Email.CONTENT_URI;
   uri = uri.buildUpon().appendQueryParameter("address_book_index_extras", "true").build();// これは使うべきではない!!非公開のoption
   String projection = { ContactsContract.Data.CONTACT_ID
      , Contacts.DISPLAY_NAME
      , ContactsContract.RawContacts.PHONETIC_NAME
      , ContactsContract.CommonDataKinds.Email.DATA1
      , ContactsContract.CommonDataKinds.Phone.NUMBER
      , ContactsContract.RawContacts.STARRED
   };
   String selection = ContactsContract.Data.MIMETYPE  + " =? OR " + ContactsContract.Data.MIMETYPE + " =?";
   String
 selectionArgs = { "vnd.android.cursor.item/name", "vnd.android.cursor.item/email_v2" };
   String sortOrder = Contacts.SORT_KEY_PRIMARY + " ASC";
   Cursor cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);
   if (cursor.moveToFirst()){
      int contactId_Index = cursor.getColumnIndex(ContactsContract.Data.CONTACT_ID);
      int diplayName_Index = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
      int email_Index = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA1);
      int starred_Index = cursor.getColumnIndex(ContactsContract.RawContacts.STARRED);
      do{
         int contactId = cursor.getInt(contactId_Index);
         String diplayName = cursor.getString(diplayName_Index);
         String email = cursor.getString(email_Index);
         boolean starred = cursor.getInt(starred_Index) == 1;

      }while(cursor.moveToNext());
   }
   cursor.close();
}

端末の電話帳は、、 ContactsContract.CommonDataKinds.Phone.CONTENT_URI を使う、

   Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
   uri = uri.buildUpon().appendQueryParameter("address_book_index_extras", "true").build();
   String projection = { ContactsContract.Data.CONTACT_ID
      , Contacts.DISPLAY_NAME
      , ContactsContract.RawContacts.PHONETIC_NAME
      , ContactsContract.CommonDataKinds.Phone.NUMBER
      , ContactsContract.RawContacts.STARRED
   };
   String selection = ContactsContract.Data.MIMETYPE  + " =? OR " + ContactsContract.Data.MIMETYPE + " =?";
   String
 selectionArgs = { "vnd.android.cursor.item/name", "vnd.android.cursor.item/phone_v2" };
    String sortOrder = Contacts.SORT_KEY_PRIMARY + " ASC";
   Cursor cursor.getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);
   if (cursor.moveToFirst()){
      int contactId_Index = cursor.getColumnIndex(ContactsContract.Data.CONTACT_ID);
      int diplayName_Index = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
      int phoneticName_Index = cursor.getColumnIndex(ContactsContract.RawContacts.PHONETIC_NAME);
      int phone_Index = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
      int starred_Index = cursor.getColumnIndex(ContactsContract.RawContacts.STARRED);
      do{
         int contactId = cursor.getInt(contactId_Index);
         String diplayName = cursor.getString(diplayName_Index);
         String phoneticName = cursor.getString(phoneticName_Index);
         String phone = cursor.getString(phone_Index);
         boolean starred = cursor.getInt(starred_Index) == 1;


      }while(cursor.moveToNext());
   }
   cursor.close();