android-ngn-stack/src/main/java/org/doubango/ngn/services/impl/NgnContactService.java
c732d49e
 /*
74ca6d11
 * Copyright (C) 2020, University of the Basque Country (UPV/EHU)
c732d49e
 *  Contact for licensing options: <licensing-mcpttclient(at)mcopenplatform(dot)com>
 *
 * The original file was part of Open Source IMSDROID
 *  Copyright (C) 2010-2011, Mamadou Diop.
 *  Copyright (C) 2011, Doubango Telecom.
 *
 *
 * Contact: Mamadou Diop <diopmamadou(at)doubango(dot)org>
 *
 * This file is part of Open Source Doubango Framework.
 *
 * This is free software: you can redistribute it and/or modify it under the terms of
 * the GNU General Public License as published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 *
 * This is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
 package org.doubango.ngn.services.impl;
 
 import android.app.Activity;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.os.Looper;
 import android.provider.ContactsContract;
 import android.util.Log;
 
 import org.doubango.ngn.NgnApplication;
 import org.doubango.ngn.NgnEngine;
 import org.doubango.ngn.model.NgnContact;
 import org.doubango.ngn.model.NgnPhoneNumber;
 import org.doubango.ngn.services.INgnContactService;
 import org.doubango.ngn.utils.NgnCallbackFunc;
 import org.doubango.ngn.utils.NgnListUtils;
 import org.doubango.ngn.utils.NgnObservableList;
 import org.doubango.ngn.utils.NgnPredicate;
 import org.doubango.ngn.utils.NgnStringUtils;
 import org.doubango.tinyWRAP.SipUri;
 import org.doubango.utils.Utils;
 
 import java.util.ArrayList;
 import java.util.List;
 
 /**@page NgnContactService_page Contact Service
  * The Contact service is used to retrieve contacts from the native address book.
  * 
  */
 
 /**
  * Service used to retrieve contacts from the native address book.
  */
 public class NgnContactService  extends NgnBaseService implements INgnContactService{
 	private final static String TAG = Utils.getTAG(NgnContactService.class.getCanonicalName());
 	public final static String PARAM_ARG_CONTACT = "NgnContact";
 	
 	protected NgnObservableList<NgnContact> mContacts;
 	protected boolean mLoading;
 	protected boolean mReady;
 	protected Looper mLocalContactObserverLooper;
 	protected ContentObserver mLocalContactObserver;
 	
 	private NgnCallbackFunc<Object> mOnBeginLoadCallback;
 	private NgnCallbackFunc<String> mOnNewPhoneNumberCallback;
 	private NgnCallbackFunc<Object> mOnEndLoadCallback;
 	
 	public NgnContactService(){
 		super();
 	}
 	
 	@Override
 	public boolean start() {
 		Log.d(TAG, "Starting...");
 		
 		if(mContacts == null){
 			mContacts = getObservableContacts();
 		}
 		/*
 		if(mLocalContactObserver == null && mLocalContactObserverLooper == null){
 			new Thread(new Runnable() { // avoid locking calling thread
 				@Override
 				public void run() {
 					Log.d(TAG, "Observer loop enter()");
 					Looper.prepare();
 					mLocalContactObserverLooper = Looper.myLooper();
 					final Handler handler = new Handler();
 					handler.post(new Runnable() { // Observer thread. Will allow us to get notifications even if the application is on background
 						@Override
 						public void run() {
 							mLocalContactObserver = new ContentObserver(handler) {
 								@Override
 								public void onChange(boolean selfChange) {
 									super.onChange(selfChange);
 									Log.d(TAG, "Native address book changed.");
 									load();
 								}
 							};
 							NgnApplication.getContext().getContentResolver().registerContentObserver(CommonDataKinds.Phone.CONTENT_URI, 
 									true, mLocalContactObserver);
 						}
 					});
 					Looper.loop();// loop() until quit() is called
 					Log.d(TAG, "Observer loop exit()");
 				}
 			}).start();
 		};
 		*/
 		return true;
 	}
 
 	@Override
 	public boolean stop() {
 		Log.d(TAG, "Stopping...");
 		
 		try{
 			if(mLocalContactObserver != null){
 				NgnApplication.getContext().getContentResolver().unregisterContentObserver(mLocalContactObserver);
 				mLocalContactObserver = null;
 			}
 			if(mLocalContactObserverLooper != null){
 				mLocalContactObserverLooper.quit();
 				mLocalContactObserverLooper = null;
 			}
 		}
 		catch(Exception e){
 			Log.e(TAG, e.toString());
 		}
 		return true;
 	}
 	
 	@Override
 	public void setOnBeginLoadCallback(NgnCallbackFunc<Object> callback){
 		mOnBeginLoadCallback = callback;
 	}
 	
 	@Override
 	public void setOnNewPhoneNumberCallback(NgnCallbackFunc<String> callback){
 		mOnNewPhoneNumberCallback = callback;
 	}
 	
 	@Override
 	public void setOnEndLoadCallback(NgnCallbackFunc<Object> callback){
 		mOnEndLoadCallback = callback;
 	}
 	
 	@Override
 	public boolean load(){
 		return load2();
 	}
 	
 	public boolean load2(){
 		mLoading = true;
 		boolean bOK = false;
 		Cursor managedCursor = null;
 		final Activity activity = NgnEngine.getInstance().getMainActivity();
 		final List<NgnContact> contactsCopy = new ArrayList<NgnContact>();
 		
 		if(mOnBeginLoadCallback != null){
 			mOnBeginLoadCallback.callback(this);
 		}
 		
 		try{
 			String phoneNumber, displayName, label;
 			NgnContact contact = null;
 			int id, type, photoId;
 			final Resources res = NgnApplication.getContext().getResources();
 			
 			if(NgnApplication.getSDKVersion() >=5 && activity != null){
 				/*synchronized(mContacts)*/{
 					final String[] projectionContacts = new String[] { 
 							android.provider.BaseColumns._ID,
 							android.provider.ContactsContract.CommonDataKinds.Phone.NUMBER,
 							android.provider.ContactsContract.CommonDataKinds.Phone.TYPE,
 							android.provider.ContactsContract.CommonDataKinds.Phone.LABEL,
 							android.provider.ContactsContract.Contacts.DISPLAY_NAME,
 							android.provider.ContactsContract.Contacts.PHOTO_ID,
 							android.provider.ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
 							};
 					
 					managedCursor = activity.managedQuery(android.provider.ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
 							projectionContacts, // Which columns to return
 							null,       // Which rows to return (all rows)
 							null,       // Selection arguments (none)
 							// Put the results in ascending order by name
 							"UPPER(" + android.provider.ContactsContract.Contacts.DISPLAY_NAME + ") ASC"
 						);
 					
 					
 					int indexPhoneContactId = managedCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.CONTACT_ID);
 					int indexPhoneType = managedCursor .getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE);
 					int indexPhoneLabel = managedCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.LABEL);
 					int indexPhoneNumber = managedCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
 					int indexPhonePhotoId = managedCursor .getColumnIndex(ContactsContract.CommonDataKinds.Phone.PHOTO_ID);
 					
 					int indexContactDisplayName = managedCursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME);
 					
 					 while(managedCursor.moveToNext()){
 						 id = managedCursor.getInt(indexPhoneContactId);
 						 type = managedCursor.getInt(indexPhoneType);
 						 label = managedCursor.getString(indexPhoneLabel);
 						 phoneNumber = managedCursor.getString(indexPhoneNumber);
 						 photoId = managedCursor.getInt(indexPhonePhotoId);
 						 
 						 if(phoneNumber != null){
 							 phoneNumber = phoneNumber.replace("-", "");
 							if(NgnStringUtils.isNullOrEmpty(label)){
 								final int resId = android.provider.ContactsContract.CommonDataKinds.Phone.getTypeLabelResource(type);
 								label = res.getString(resId);
 							}
 							 if(contact == null || contact.getId() != id){
 								displayName = managedCursor.getString(indexContactDisplayName);
 								contact = newContact(id, displayName);
 								if(photoId != 0){
 									 contact.setPhotoId(photoId);
 								}
 								contactsCopy.add(contact);
 							 }
 							 contact.addPhoneNumber(NgnPhoneNumber.fromAndroid2LocalType(type), phoneNumber, label);
 							 if(mOnNewPhoneNumberCallback != null){
 								 mOnNewPhoneNumberCallback.callback(phoneNumber);
 							 }
 						 }
 					 }
 					 
 					 mLoading = false;
 					 mReady = true;
 					 bOK = true;
 				}
 			}
 		}
 		catch(Exception e){
 			e.printStackTrace();
 			
 			mLoading = false;
 			mReady = false;
 		}
 		finally{
 			if(managedCursor != null){
 				activity.stopManagingCursor(managedCursor);
 				
 				managedCursor.close();
 			}
 		}
 		
 		if(bOK){
 			synchronized(mContacts){
 				mContacts.clear();
 				mContacts.add(contactsCopy);
 			}
 		}
 		
 		if(mOnEndLoadCallback != null){
 			mOnEndLoadCallback.callback(this);
 		}
 		
 		return bOK;
 	}
 
 	@Override
 	public boolean isLoading() {
 		return mLoading;
 	}
 
 	@Override
 	public boolean isReady(){
 		return mReady;
 	}
 	
 	@Override
 	public NgnObservableList <NgnContact> getObservableContacts() {
 		if(mContacts == null){
 			mContacts = new NgnObservableList<NgnContact>(true);
 		}
 		return mContacts;
 	}
 
 	@Override
 	public NgnContact newContact(int id, String displayName){
 		return new NgnContact(id, displayName);
 	}
 	
 	@Override
 	public NgnContact getContactByUri(String uri) {
 		final SipUri sipUri = new SipUri(uri);
 		NgnContact contact = null;
 		if(sipUri.isValid()){
 			contact = getContactByPhoneNumber(sipUri.getUserName());
 		}
 		sipUri.delete();
 		return contact;
 	}
 
 	@Override
 	public NgnContact getContactByPhoneNumber(String anyPhoneNumber) {
 		return NgnListUtils.getFirstOrDefault(mContacts.getList(), new NgnContact.ContactFilterByAnyPhoneNumber(anyPhoneNumber));
 	}
 
 	static class ContactFilterById implements NgnPredicate<NgnContact>{
 		private final int mId;
 		ContactFilterById(int id){
 			mId = id;
 		}
 		@Override
 		public boolean apply(NgnContact contact) {
 			return (contact != null && contact.getId() == mId);
 		}
 	}
 	@Override
 	public boolean clearService(){
 		return true;
 	}
 }