001 package com.skype.api;
002
003 import java.io.IOException;
004 import java.util.*;
005 import com.skype.ipc.*;
006 /**
007 * This class encapsulates functionality for looking up contacts on the Skype network. Contacts can be searched by portion of their name, e-mail address, language preferences, etc. <br><br>Contact search is asynchronous. ContactSearch.Submit is a non-blocking function that initiates the search. Upon finding a matching contact, ContactSearch.OnNewResult event gets fired, that gives you the reference to the discovered contact. You can get up to 100 matching contacts per search. Note that you will need to keep a live reference of the ContactSearch object while the search is doing its work. <br><br>So, to perform a contact search: <br> - create a contact search object <br> - specify search terms and conditions <br> - submit search <br> - in ContactSearch.OnNewResult callback, update your UI <br> - in ContactSearch.OnChange, check for terminal values of P_CONTACT_SEARCH_STATUS and update the UI accordingly. <br><br>When the search has done its job, the ContactSearch.P_CONTACT_SEARCH_STATUS property will go to one of the terminal values. <br><br>The terminal values are: <br> - FINISHED - the search has stopped. Note that this does not mean any matches were actually found. <br> - FAILED - the search has failed. <br> - EXTENDABLE - this state should be considered the same as FINISHED. The feature of extending long search results is about to be deprecated. It is still possible for search objects to occasionally reach that state, so it should be handled in the UI (as FINISHED), but the extending feature itself should not be implemented in your UI. <br><br>There are three methods to create the ContactSearch objects. <br><br>A) Skype.CreateIdentitySearch <br><br>This method takes a string argument and looks for exact matches against Contact.P_SKYPENAME property. So for example, identity search for "echo" will return 0 results and search for "echo123" will return exactly one. <br><br>Identity in this case means skypename - contact search does not work with PSTN type contacts. However, it does work for SKYPE type contacts that have supplied P_PHONE_HOME, P_PHONE_OFFICE or P_PHONE_MOBILE values in their account data. To search for those, you will need to use complex search (see below). <br><br>Note that you should always check for boolean return value of the CreateIdentitySearch method. If the user submits a string that is not a valid skypename, the method will return false and the ContactSearchRef argument will return as NULL. <br><br>B) Skype.CreateBasicContactSearch <br><br>This method takes a string argument and looks for non-exact matches against both P_SKYPENAME and P_FULLNAME properties of the contact. If you intend to implement a simple, one-input search feature - this is the best method for you. The non-exact matching operates similarly to the SQL LIKE condition. <br><br>C) Skype.CreateContactSearch <br><br>This method enables you to implement advanced contact search, matching against multiple seach criteria. It takes no input arguments and expects search criteria to be added to the already constructed search object. <br><br>Criteria can be added with ContactSearch.AddStrTerm and ContactSearch.AddIntTerm methods. <br><br>These methods take Contact class porperty ID, condition, and the match pattern as inputs. <br><br>Only the following Contact properties can be used for search: <br> - P_SKYPENAME <br> - P_FULLNAME <br> - P_BIRTHDAY (uint) <br> - P_GENDER (uint: 1-male, 2-female) <br> - P_LANGUAGES <br> - P_COUNTRY <br> - P_PROVINCE <br> - P_CITY <br> - P_PHONE_HOME <br> - P_PHONE_OFFICE <br> - P_PHONE_MOBILE <br> - P_EMAILS <br> - P_HOMEPAGE <br> - P_ABOUT <br><br>String searches are case insensitive, i.e. search for echo123 also matches ECHO123 <br><br>When adding multiple criteria, default behaviour is that the criterions are additive. I.e. a term skypename == "joe" followed by term country == "us" will result in intersection between all joes and everybody in US. <br><br>You can explicitly add an "OR" instead of "AND" between conditions, using the AddOr method. <br><br>By default, AND criteria are grouped together, before OR's, so that: <br><br>AddTerm(condition1) <br>AddTerm(condition2) <br>AddOr() <br>AddTerm(condition3) <br>AddTerm(condition4) <br><br>will result in the following logical statement: <br>(condition1 AND condition2) OR (condition3 AND condition4) <br><br>However, you can add "global" critera, by using the add_to_subs argument of the AddXX methods. <br><br>AddTerm(condition1) <br>AddTerm(condition2) <br>AddOr() <br>AddTerm(condition3) <br>AddTerm(condition4, add_to_subs=true) <br><br>which would result in: <br>(condition1 AND condition2 AND condition4) OR (condition3 AND condition4) <br><br><br>Every one of the contact properties can only be used once, per search. For example, you cannot create a search for two different P_FULLNAME patterns. The &valid argument will still return tue if you do this, but the last criteria for any given property will override all previous ones. So, a search like this: <br><br>cs->AddStrTerm(Contact.P_FULLNAME, ContactSearch.EQ, "John Smith", isValid); <br>cs->AddOr(); <br>cs->AddStrTerm(Contact.P_FULLNAME, ContactSearch.EQ, "Ivan Sidorov", isValid); <br><br>will only return matches for "Ivan Sidorov" and none for "John Smith". <br><br>Some of the contact properties are automatically combined for purposes of search. <br><br>A search for P_SKYPENAME also returns matches from the P_FULLNAME property and vice versa. <br><br>So that this: <br>cs->AddStrTerm(Contact.P_SKYPENAME, ContactSearch.EQ, "john.smith", isValid); <br><br>..and this: <br>cs->AddStrTerm(Contact.P_FULLNAME, ContactSearch.EQ, "John Smith", isValid); <br><br>..and this: <br>cs->AddStrTerm(Contact.P_SKYPENAME, ContactSearch.EQ, "john.smith", isValid); <br>cs->AddOr(); <br>cs->AddStrTerm(Contact.P_FULLNAME, ContactSearch.EQ, "John Smith", isValid); <br><br>..all search from both the P_FULLNAME and P_SKYPENAME fields. <br><br><br>Before using ContactGroup.Submit to start the search, you should always check whether the search criteria ended up being valid. This you can do with ContactSearch.IsValid method. <br><br>As you probably noticed, each of the AddXX methods also return a validity check boolean. However, it is a better practice to do the overall check as well, even if all the individual search criteria ended up looking Ok. <br><br>For example, lets take a search for contact's e-mail. This can be done with two different methods. Firstly we can use the ContactSearch.AddEmailTerm method. This method will actually validate whether the input is a valid e-mail address: <br><br>cs->AddEmailTerm ("test@test@test", isValid); <br>will return the isValid argument as false. <br><br>However, you can also add the e-mail search criterion as a simple string, like this: <br><br>cs->AddStrTerm(Contact.P_EMAILS, ContactSearch.EQ, "test@test@test@", isValid); <br>in which case the isValid will return true. <br><br>However, if you then check entire search object with: <br><br>cs->IsValid(isValid); <br><br>the isValid will correctly return false. <br>
008 */
009
010
011 public class ContactSearch extends SkypeObject {
012
013
014 public interface ContactSearchListener {
015 /** This event gets called when there are changes to ContactSearch properties defined in ContactSearch.PROPERTY */
016 public void OnPropertyChange(SkypeObject obj, PROPERTY prop, Object value);
017
018 /**This callback is fired when a new matching contact has been found during the search. <br>*/
019 public void OnNewResult(SkypeObject obj, Contact contact, int rankValue);
020
021 }
022
023 public ContactSearch(int oid, Skype skype) {
024 super(oid,skype);
025 }
026
027 private static final int MODULE_ID = 1;
028
029 public static final int moduleID() {
030 return MODULE_ID;
031 }
032
033 /** Properties of the ContactSearch class */
034 public enum PROPERTY {
035
036 /** type: STATUS */
037 contact_search_status(200);
038
039 private static final Map<Integer,PROPERTY> lookup = new HashMap<Integer,PROPERTY>();
040
041 static {
042 for(PROPERTY s : EnumSet.allOf(PROPERTY.class))
043 lookup.put(s.getId(), s);
044 }
045
046 private final int id;
047
048 private PROPERTY(int value) {
049 this.id = value;
050 }
051
052 public int getId() { return id; }
053
054 public static PROPERTY get(int code) {
055 return lookup.get(code);
056 }
057
058 public static PROPERTY fromString(String s) {
059 for (PROPERTY p : lookup.values()) {
060 if (p.toString() == s) {
061 return p;
062 }
063 }
064 return null;
065 }
066 }
067
068 public Object GetPropertyAsEnum(int propid) {
069 return PROPERTY.get(propid);
070 }
071
072 public String GetStrProperty(PROPERTY prop) {
073 //check in propcache if so then return
074 if (mPropCache.containsKey(new Integer(prop.id))){
075 String value = (String)mPropCache.get(prop.id);
076 if (value != null && !(value.length() == 0) ){
077 return value;
078 }
079 }
080 //else get from skypekit...
081 GetPropertyRequest request = new GetPropertyRequest(1, mObjectId, prop.id);
082
083 String string = null;
084 GetPropertyResponse r = skype.GetProperty(request);
085 if (r != null){
086 string = r.GetAsString();
087 }
088
089 if (string != null)
090 {
091 mPropCache.put(new Integer(prop.id), string);
092 }
093 return string;
094 }
095
096 public int GetIntProperty(PROPERTY prop) {
097 //check in propcache if so then return
098 if (mPropCache.containsKey(new Integer(prop.id))){
099 int value = ((Integer)mPropCache.get(prop.id)).intValue();
100 if (value != 0){
101 return value;
102 }
103 }
104 //else get from skypekit...
105 GetPropertyRequest request = new GetPropertyRequest(moduleID(), mObjectId, prop.id);
106
107 Integer integer = null;
108 GetPropertyResponse r = skype.GetProperty(request);
109 if (r != null){
110 integer = r.GetAsInt();
111 }
112
113 if (integer != null)
114 {
115 mPropCache.put(new Integer(prop.id), integer);
116 return integer.intValue();
117 }
118 else
119 {
120 return 0;
121 }
122 }
123
124 public boolean GetBooleanProperty(PROPERTY prop) {
125 //check in propcache if so then return
126 if (mPropCache.containsKey(new Integer(prop.id))){
127 return ((Boolean)mPropCache.get(prop.id)).booleanValue();
128 }
129 //else get from skypekit...
130 GetPropertyRequest request = new GetPropertyRequest(moduleID(), mObjectId, prop.id);
131
132 Boolean boolResp = null;
133 GetPropertyResponse r = skype.GetProperty(request);
134 if (r != null){
135 boolResp = r.GetAsBoolean();
136 }
137
138 if (boolResp != null)
139 {
140 mPropCache.put(new Integer(prop.id), boolResp);
141 return boolResp.booleanValue();
142 }
143 else
144 {
145 return false;
146 }
147 }
148
149 public byte [] GetBinProperty(PROPERTY prop) {
150 //get from skypekit...
151 GetPropertyRequest request = new GetPropertyRequest(1, mObjectId, prop.id);
152
153 byte [] data = null;
154 GetPropertyResponse r = skype.GetProperty(request);
155 if (r != null) {
156 data = r.GetAsBinary();
157 }
158 return data;
159 }
160
161 /**
162 Possible values for the ContactSearch.P_STATUS property. <br> */
163 public enum STATUS {
164
165 /** Transient state, obtained after submission and actually initiating the search on the network. <br>*/
166 CONSTRUCTION(1),
167
168 /** Waiting for results to show up. This is a transient state. <br>*/
169 PENDING(2),
170
171 /** Enough matches are found. No more OnNewResult events will fire. The feature of extending long search results is about to be deprecated. It is still possible for search objects to occasionally reach that state, so it should be handled in the UI (as FINISHED), but the extending feature itself should not be implemented in your UI. <br>*/
172 EXTENDABLE(3),
173
174 /** The search is finished. No more matches are expected. This is a terminal state. <br>*/
175 FINISHED(4),
176
177 /** ContactSearch failed. Better check if the search terms made any sense, with ContactSearch.IsValid. This is a terminal state. <br>*/
178 FAILED(5);
179
180 private static final Map<Integer,STATUS> lookup = new HashMap<Integer,STATUS>();
181
182 static {
183 for(STATUS s : EnumSet.allOf(STATUS.class))
184 lookup.put(s.getId(), s);
185 }
186
187 private final int id;
188
189 private STATUS(int value) {
190 this.id = value;
191 }
192
193 public int getId() { return id; }
194
195 public static STATUS get(int code) {
196 return lookup.get(code);
197 }
198
199 public static STATUS fromString(String s) {
200 for (STATUS p : lookup.values()) {
201 if (p.toString() == s) {
202 return p;
203 }
204 }
205 return null;
206 }
207 }
208
209 /**
210 *construct CONTACT_BIRTHDAY term based on current time
211 * @param min_age_in_years
212 * @param add_to_subs
213 * @return valid
214 */
215 public boolean AddMinAgeTerm( int min_age_in_years, boolean add_to_subs) {
216
217 Request request = null;
218 try {
219 request = new XCallRequest(1,1);
220 } catch (IOException e) {
221 e.printStackTrace();
222 if (skype.errorListener != null)
223 skype.errorListener.OnSkypeKitFatalError();
224 }
225 request.addParm('O',0,mObjectId);
226 request.addParm('u',1,min_age_in_years);
227 request.addParm('b',2,add_to_subs);
228
229 Response r = skype.XCall((XCallRequest)request);
230
231 if (r.isErrCall())
232 return false;
233
234 boolean valid = false;
235 valid = r.GetAsBoolean(1);
236 return valid;
237 }
238
239 /**
240 *construct CONTACT_BIRTHDAY term based on current time
241 * @param max_age_in_years
242 * @param add_to_subs
243 * @return valid
244 */
245 public boolean AddMaxAgeTerm( int max_age_in_years, boolean add_to_subs) {
246
247 Request request = null;
248 try {
249 request = new XCallRequest(1,2);
250 } catch (IOException e) {
251 e.printStackTrace();
252 if (skype.errorListener != null)
253 skype.errorListener.OnSkypeKitFatalError();
254 }
255 request.addParm('O',0,mObjectId);
256 request.addParm('u',1,max_age_in_years);
257 request.addParm('b',2,add_to_subs);
258
259 Response r = skype.XCall((XCallRequest)request);
260
261 if (r.isErrCall())
262 return false;
263
264 boolean valid = false;
265 valid = r.GetAsBoolean(1);
266 return valid;
267 }
268
269 /**
270 *Adds a search term against Contact.P_EMAILS property and pre-validates the value given in the email argument. <br>
271 * @param email e-mail addres to search for. <br>
272 * @param add_to_subs This argument enables you to group conditions. See ContactSearch class details for more information. <br>
273 * @return valid Returns false if the value in email property did not look like a valid email address. <br>
274 */
275 public boolean AddEmailTerm( String email, boolean add_to_subs) {
276
277 Request request = null;
278 try {
279 request = new XCallRequest(1,3);
280 } catch (IOException e) {
281 e.printStackTrace();
282 if (skype.errorListener != null)
283 skype.errorListener.OnSkypeKitFatalError();
284 }
285 request.addParm('O',0,mObjectId);
286 request.addParm('S',1,email);
287 request.addParm('b',2,add_to_subs);
288
289 Response r = skype.XCall((XCallRequest)request);
290
291 if (r.isErrCall())
292 return false;
293
294 boolean valid = false;
295 valid = r.GetAsBoolean(1);
296 return valid;
297 }
298
299 /**
300 * @param language
301 * @param add_to_subs
302 * @return valid
303 */
304 public boolean AddLanguageTerm( String language, boolean add_to_subs) {
305
306 Request request = null;
307 try {
308 request = new XCallRequest(1,4);
309 } catch (IOException e) {
310 e.printStackTrace();
311 if (skype.errorListener != null)
312 skype.errorListener.OnSkypeKitFatalError();
313 }
314 request.addParm('O',0,mObjectId);
315 request.addParm('S',1,language);
316 request.addParm('b',2,add_to_subs);
317
318 Response r = skype.XCall((XCallRequest)request);
319
320 if (r.isErrCall())
321 return false;
322
323 boolean valid = false;
324 valid = r.GetAsBoolean(1);
325 return valid;
326 }
327
328 /**
329 List of available matching conditions that can be used in AddTerm methods. <br> */
330 public enum CONDITION {
331
332 /** Equals <br>*/
333 EQ(0),
334
335 /** Is greater than <br>*/
336 GT(1),
337
338 /** Is greater or equal. <br>*/
339 GE(2),
340
341 /** Is less than <br>*/
342 LT(3),
343
344 /** Less or equal <br>*/
345 LE(4),
346
347 /** Start of a word macthes exactly (string properties only). <br>*/
348 PREFIX_EQ(5),
349
350 /** Start of a word is greater or equal (string properties only). <br>*/
351 PREFIX_GE(6),
352
353 /** Start of a word is less or equal (string properties only). <br>*/
354 PREFIX_LE(7),
355
356 /** Contains the word (string properties only). <br>*/
357 CONTAINS_WORDS(8),
358
359 /** One of the words starts with searched value (string properties only). <br>*/
360 CONTAINS_WORD_PREFIXES(9);
361
362 private static final Map<Integer,CONDITION> lookup = new HashMap<Integer,CONDITION>();
363
364 static {
365 for(CONDITION s : EnumSet.allOf(CONDITION.class))
366 lookup.put(s.getId(), s);
367 }
368
369 private final int id;
370
371 private CONDITION(int value) {
372 this.id = value;
373 }
374
375 public int getId() { return id; }
376
377 public static CONDITION get(int code) {
378 return lookup.get(code);
379 }
380
381 public static CONDITION fromString(String s) {
382 for (CONDITION p : lookup.values()) {
383 if (p.toString() == s) {
384 return p;
385 }
386 }
387 return null;
388 }
389 }
390
391 /**
392 *Adds a string search term to a custom contact search object. <br>
393 * @param prop Following Contact class string propkeys can be used for Contact search: <br> - Contact.P_SKYPENAME <br> - Contact.P_FULLNAME <br> - Contact.P_LANGUAGES <br> - Contact.P_COUNTRY <br> - Contact.P_PROVINCE <br> - Contact.P_CITY <br> - Contact.P_PHONE_HOME <br> - Contact.P_PHONE_OFFICE <br> - Contact.P_PHONE_MOBILE <br> - Contact.P_EMAILS <br> - Contact.P_HOMEPAGE <br> - Contact.P_ABOUT <br>Note that while Contact.P_EMAILS is technically a string and can be used in this method, it is recommended that you use ContactSearch.AddEmailTerm method instead. <br>
394 * @param cond Search condition (ContactSearch.CONDITION) <br>
395 * @param value Value to match against. <br>
396 * @param add_to_subs This argument enables you to group conditions. See ContactSearch class details for more information. <br>
397 * @return valid Returns true if the ContactSearch term-set remains valid after adding this term. <br>
398 */
399 public boolean AddStrTerm( int prop, CONDITION cond, String value, boolean add_to_subs) {
400
401 Request request = null;
402 try {
403 request = new XCallRequest(1,5);
404 } catch (IOException e) {
405 e.printStackTrace();
406 if (skype.errorListener != null)
407 skype.errorListener.OnSkypeKitFatalError();
408 }
409 request.addParm('O',0,mObjectId);
410 request.addParm('e',1,prop);
411 request.addParm('e',2,cond.getId());
412 request.addParm('S',3,value);
413 request.addParm('b',4,add_to_subs);
414
415 Response r = skype.XCall((XCallRequest)request);
416
417 if (r.isErrCall())
418 return false;
419
420 boolean valid = false;
421 valid = r.GetAsBoolean(1);
422 return valid;
423 }
424
425 /**
426 *Adds a search term to a custom contact search object. For now, there are only two searchable Contact properties that are integers, so this can oly be used for Contact.P_BIRTHDAY and Contact.P_GENDER. <br>
427 * @param prop Propkey to search for. Either Contact.P_BIRTHDAY or Contact.P_GENDER <br>
428 * @param cond Search condition (ContactSearch.CONDITION) <br>
429 * @param value Value to match against. <br>
430 * @param add_to_subs This argument enables you to group conditions. See ContactSearch class details for more information. <br>
431 * @return valid Returns true if the ContactSearch term-set remains valid after adding this term. <br>
432 */
433 public boolean AddIntTerm( int prop, CONDITION cond, int value, boolean add_to_subs) {
434
435 Request request = null;
436 try {
437 request = new XCallRequest(1,6);
438 } catch (IOException e) {
439 e.printStackTrace();
440 if (skype.errorListener != null)
441 skype.errorListener.OnSkypeKitFatalError();
442 }
443 request.addParm('O',0,mObjectId);
444 request.addParm('e',1,prop);
445 request.addParm('e',2,cond.getId());
446 request.addParm('u',3,value);
447 request.addParm('b',4,add_to_subs);
448
449 Response r = skype.XCall((XCallRequest)request);
450
451 if (r.isErrCall())
452 return false;
453
454 boolean valid = false;
455 valid = r.GetAsBoolean(1);
456 return valid;
457 }
458
459 /**
460 *used to group terms (AddTerm(1), AddTerm(2), Or(), AddTerm(3), AddTerm(4), etc)
461 */
462 public void AddOr() {
463
464 Request request = null;
465 try {
466 request = new XCallRequest(1,7);
467 } catch (IOException e) {
468 e.printStackTrace();
469 if (skype.errorListener != null)
470 skype.errorListener.OnSkypeKitFatalError();
471 }
472 request.addParm('O',0,mObjectId);
473
474 skype.XCall((XCallRequest)request);
475 }
476
477 /**
478 *checks that terms list is non-empty and does not contain unsupported keys
479 * @return result
480 */
481 public boolean IsValid() {
482
483 Request request = null;
484 try {
485 request = new XCallRequest(1,8);
486 } catch (IOException e) {
487 e.printStackTrace();
488 if (skype.errorListener != null)
489 skype.errorListener.OnSkypeKitFatalError();
490 }
491 request.addParm('O',0,mObjectId);
492
493 Response r = skype.XCall((XCallRequest)request);
494
495 if (r.isErrCall())
496 return false;
497
498 boolean result = false;
499 result = r.GetAsBoolean(1);
500 return result;
501 }
502
503 /**
504 *launch search
505 */
506 public void Submit() {
507
508 Request request = null;
509 try {
510 request = new XCallRequest(1,9);
511 } catch (IOException e) {
512 e.printStackTrace();
513 if (skype.errorListener != null)
514 skype.errorListener.OnSkypeKitFatalError();
515 }
516 request.addParm('O',0,mObjectId);
517
518 skype.XCall((XCallRequest)request);
519 }
520
521 /**
522 *extend if search is EXTENDABLE
523 */
524 public void Extend() {
525
526 Request request = null;
527 try {
528 request = new XCallRequest(1,10);
529 } catch (IOException e) {
530 e.printStackTrace();
531 if (skype.errorListener != null)
532 skype.errorListener.OnSkypeKitFatalError();
533 }
534 request.addParm('O',0,mObjectId);
535
536 skype.XCall((XCallRequest)request);
537 }
538
539 /**
540 *releases results that are not referenced from elsewhere
541 */
542 public void Release() {
543
544 Request request = null;
545 try {
546 request = new XCallRequest(1,12);
547 } catch (IOException e) {
548 e.printStackTrace();
549 if (skype.errorListener != null)
550 skype.errorListener.OnSkypeKitFatalError();
551 }
552 request.addParm('O',0,mObjectId);
553
554 skype.XCall((XCallRequest)request);
555 }
556
557 /**
558 *result list is dynamically updated
559 * @param from
560 * @param count
561 * @return contacts
562 */
563 public Contact [] GetResults( int from, int count) {
564
565 Request request = null;
566 try {
567 request = new XCallRequest(1,11);
568 } catch (IOException e) {
569 e.printStackTrace();
570 if (skype.errorListener != null)
571 skype.errorListener.OnSkypeKitFatalError();
572 }
573 request.addParm('O',0,mObjectId);
574 request.addParm('u',1,from);
575 request.addParm('u',2,count);
576
577 Response r = skype.XCall((XCallRequest)request);
578
579 if (r.isErrCall())
580 return null;
581
582 Vector<Contact> contacts = new Vector<Contact>();
583 while (r.HasMore(1))
584 {
585 int oid = 0;
586 Contact contact = null;
587 oid = r.GetOid(1);
588 if (oid != AbstractDecoder.NULL_VALUE) {
589 contact = (Contact)skype.factory(Contact.moduleID(), oid, skype);
590 }
591 contacts.add(contact);
592 }
593 return contacts.toArray(new Contact[contacts.size()]);
594
595 }
596
597
598 }