001 package com.skype.api;
002
003 import com.skype.ipc.SidRoot;
004 import com.skype.ipc.SidObject;
005 import com.skype.ipc.EnumConverting;
006 import com.skype.ipc.PropertyEnumConverting;
007 import com.skype.ipc.Decoding;
008 import com.skype.ipc.Encoding;
009 import com.skype.ipc.Encoding;
010 import java.io.IOException;
011 import com.skype.ipc.PropertyEnumConverting;
012 import com.skype.ipc.SidGetResponding;
013
014 /**
015 * 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.
016 *
017 * 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.
018 *
019 * So, to perform a contact search:
020 * - create a contact search object
021 * - specify search terms and conditions
022 * - submit search
023 * - in ContactSearch.OnNewResult callback, update your UI
024 * - in ContactSearch.OnChange, check for terminal values of P_CONTACT_SEARCH_STATUS and update the UI accordingly.
025 *
026 * When the search has done its job, the ContactSearch.P_CONTACT_SEARCH_STATUS property will go to one of the terminal values.
027 *
028 * The terminal values are:
029 * - FINISHED - the search has stopped. Note that this does not mean any matches were actually found.
030 * - FAILED - the search has failed.
031 * - 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.
032 *
033 * There are three methods to create the ContactSearch objects.
034 *
035 * A) Skype.CreateIdentitySearch
036 *
037 * 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.
038 *
039 * 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).
040 *
041 * 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.
042 *
043 * B) Skype.CreateBasicContactSearch
044 *
045 * 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.
046 *
047 * C) Skype.CreateContactSearch
048 *
049 * 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.
050 *
051 * Criteria can be added with ContactSearch.AddStrTerm and ContactSearch.AddIntTerm methods.
052 *
053 * These methods take Contact class porperty ID, condition, and the match pattern as inputs.
054 *
055 * Only the following Contact properties can be used for search:
056 * - P_SKYPENAME
057 * - P_FULLNAME
058 * - P_BIRTHDAY (uint)
059 * - P_GENDER (uint: 1-male, 2-female)
060 * - P_LANGUAGES
061 * - P_COUNTRY
062 * - P_PROVINCE
063 * - P_CITY
064 * - P_PHONE_HOME
065 * - P_PHONE_OFFICE
066 * - P_PHONE_MOBILE
067 * - P_EMAILS
068 * - P_HOMEPAGE
069 * - P_ABOUT
070 *
071 * String searches are case insensitive, i.e. search for echo123 also matches ECHO123
072 *
073 * 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.
074 *
075 * You can explicitly add an "OR" instead of "AND" between conditions, using the AddOr method.
076 *
077 * By default, AND criteria are grouped together, before OR's, so that:
078 *
079 * AddTerm(condition1)
080 * AddTerm(condition2)
081 * AddOr()
082 * AddTerm(condition3)
083 * AddTerm(condition4)
084 *
085 * will result in the following logical statement:
086 * (condition1 AND condition2) OR (condition3 AND condition4)
087 *
088 * However, you can add "global" critera, by using the add_to_subs argument of the AddXX methods.
089 *
090 * AddTerm(condition1)
091 * AddTerm(condition2)
092 * AddOr()
093 * AddTerm(condition3)
094 * AddTerm(condition4, add_to_subs=true)
095 *
096 * which would result in:
097 * (condition1 AND condition2 AND condition4) OR (condition3 AND condition4)
098 *
099 *
100 * 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:
101 *
102 * cs->AddStrTerm(Contact.P_FULLNAME, ContactSearch.EQ, "John Smith", isValid);
103 * cs->AddOr();
104 * cs->AddStrTerm(Contact.P_FULLNAME, ContactSearch.EQ, "Ivan Sidorov", isValid);
105 *
106 * will only return matches for "Ivan Sidorov" and none for "John Smith".
107 *
108 * Some of the contact properties are automatically combined for purposes of search.
109 *
110 * A search for P_SKYPENAME also returns matches from the P_FULLNAME property and vice versa.
111 *
112 * So that this:
113 * cs->AddStrTerm(Contact.P_SKYPENAME, ContactSearch.EQ, "john.smith", isValid);
114 *
115 * ..and this:
116 * cs->AddStrTerm(Contact.P_FULLNAME, ContactSearch.EQ, "John Smith", isValid);
117 *
118 * ..and this:
119 * cs->AddStrTerm(Contact.P_SKYPENAME, ContactSearch.EQ, "john.smith", isValid);
120 * cs->AddOr();
121 * cs->AddStrTerm(Contact.P_FULLNAME, ContactSearch.EQ, "John Smith", isValid);
122 *
123 * ..all search from both the P_FULLNAME and P_SKYPENAME fields.
124 *
125 *
126 * 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.
127 *
128 * 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.
129 *
130 * 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:
131 *
132 * cs->AddEmailTerm ("test@test@test", isValid);
133 * will return the isValid argument as false.
134 *
135 * However, you can also add the e-mail search criterion as a simple string, like this:
136 *
137 * cs->AddStrTerm(Contact.P_EMAILS, ContactSearch.EQ, "test@test@test@", isValid);
138 * in which case the isValid will return true.
139 *
140 * However, if you then check entire search object with:
141 *
142 * cs->IsValid(isValid);
143 *
144 * the isValid will correctly return false.
145 */
146 public final class ContactSearch extends SidObject {
147 /** Possible values for the ContactSearch.P_STATUS property. */
148 public enum Status implements EnumConverting {
149 /** Transient state, obtained after submission and actually initiating the search on the network. */
150 CONSTRUCTION(1),
151 /** Waiting for results to show up. This is a transient state. */
152 PENDING (2),
153 /** 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. */
154 EXTENDABLE (3),
155 /** The search is finished. No more matches are expected. This is a terminal state. */
156 FINISHED (4),
157 /** ContactSearch failed. Better check if the search terms made any sense, with ContactSearch.IsValid. This is a terminal state. */
158 FAILED (5);
159 private final int key;
160 Status(int key) {
161 this.key = key;
162 };
163 public int getId() { return key; }
164 public EnumConverting getDefault() { return CONSTRUCTION; } public EnumConverting convert(int from) { return Status.get(from); }
165 public EnumConverting[] getArray(final int size) { return new Status[size]; }
166 public static Status get(int from) {
167 switch (from) {
168 case 1: return CONSTRUCTION;
169 case 2: return PENDING;
170 case 3: return EXTENDABLE;
171 case 4: return FINISHED;
172 case 5: return FAILED;
173 }
174 return CONSTRUCTION;
175 }
176 public static final int CONSTRUCTION_VALUE = 1;
177 public static final int PENDING_VALUE = 2;
178 public static final int EXTENDABLE_VALUE = 3;
179 public static final int FINISHED_VALUE = 4;
180 public static final int FAILED_VALUE = 5;
181 }
182 /** List of available matching conditions that can be used in AddTerm methods. */
183 public enum Condition implements EnumConverting {
184 /** Equals */
185 EQ (0),
186 /** Is greater than */
187 GT (1),
188 /** Is greater or equal. */
189 GE (2),
190 /** Is less than */
191 LT (3),
192 /** Less or equal */
193 LE (4),
194 /** Start of a word macthes exactly (string properties only). */
195 PREFIX_EQ (5),
196 /** Start of a word is greater or equal (string properties only). */
197 PREFIX_GE (6),
198 /** Start of a word is less or equal (string properties only). */
199 PREFIX_LE (7),
200 /** Contains the word (string properties only). */
201 CONTAINS_WORDS (8),
202 /** One of the words starts with searched value (string properties only). */
203 CONTAINS_WORD_PREFIXES(9);
204 private final int key;
205 Condition(int key) {
206 this.key = key;
207 };
208 public int getId() { return key; }
209 public EnumConverting getDefault() { return EQ; }
210 public EnumConverting convert(int from) { return Condition.get(from); }
211 public EnumConverting[] getArray(final int size) { return new Condition[size]; }
212 public static Condition get(int from) {
213 switch (from) {
214 case 0: return EQ;
215 case 1: return GT;
216 case 2: return GE;
217 case 3: return LT;
218 case 4: return LE;
219 case 5: return PREFIX_EQ;
220 case 6: return PREFIX_GE;
221 case 7: return PREFIX_LE;
222 case 8: return CONTAINS_WORDS;
223 case 9: return CONTAINS_WORD_PREFIXES;
224 }
225 return EQ;
226 }
227 public static final int EQ_VALUE = 0;
228 public static final int GT_VALUE = 1;
229 public static final int GE_VALUE = 2;
230 public static final int LT_VALUE = 3;
231 public static final int LE_VALUE = 4;
232 public static final int PREFIX_EQ_VALUE = 5;
233 public static final int PREFIX_GE_VALUE = 6;
234 public static final int PREFIX_LE_VALUE = 7;
235 public static final int CONTAINS_WORDS_VALUE = 8;
236 public static final int CONTAINS_WORD_PREFIXES_VALUE = 9;
237 }
238 private final static byte[] P_CONTACT_SEARCH_STATUS_req = {(byte) 90,(byte) 71,(byte) 200,(byte) 1,(byte) 93,(byte) 1};
239 /** Properties of the ContactSearch class */
240 public enum Property implements PropertyEnumConverting {
241 P_UNKNOWN (0,0,null,0,null),
242 P_CONTACT_SEARCH_STATUS(200, 1, P_CONTACT_SEARCH_STATUS_req, 0, Status.get(0));
243 private final int key;
244 private final int idx;
245 private final byte[] req;
246 private final int mod;
247 private final EnumConverting enumConverter;
248 Property(int key, int idx, byte[] req, int mod, EnumConverting converter) {
249 this.key = key;
250 this.idx = idx;
251 this.req = req;
252 this.mod = mod;
253 this.enumConverter = converter;
254 };
255 public boolean isCached() { return idx > 0; }
256 public int getIdx() { return idx; }
257 public int getId() { return key; }
258 public byte[] getRequest() { return req; }
259 public EnumConverting getDefault() { return P_UNKNOWN; }
260 public int getModuleId() { return mod; }
261 public EnumConverting getEnumConverter() { return enumConverter; }
262 public EnumConverting convert(final int from) { return Property.get(from); }
263 public EnumConverting[] getArray(final int size) { return new Property[size]; }
264 public static Property get(final int from) {
265 switch (from) {
266 case 200: return P_CONTACT_SEARCH_STATUS;
267 }
268 return P_UNKNOWN;
269 }
270 public static final int P_CONTACT_SEARCH_STATUS_VALUE = 200;
271 }
272 private final static byte[] addMinAgeTerm_req = {(byte) 90,(byte) 82,(byte) 1,(byte) 1};
273 /** construct CONTACT_BIRTHDAY term based on current time * @param minAgeInYears
274 * @param addToSubs
275 * @return valid
276 */
277 public boolean addMinAgeTerm(int minAgeInYears, boolean addToSubs) {
278 try {
279 return sidDoRequest(addMinAgeTerm_req)
280 .addUintParm(1, minAgeInYears)
281 .addBoolParm(2, addToSubs)
282 .endRequest().getBoolParm(1, true);
283 } catch(IOException e) {
284 mSidRoot.sidOnFatalError(e);
285 return false
286 ;}
287 }
288 private final static byte[] addMaxAgeTerm_req = {(byte) 90,(byte) 82,(byte) 1,(byte) 2};
289 /** construct CONTACT_BIRTHDAY term based on current time * @param maxAgeInYears
290 * @param addToSubs
291 * @return valid
292 */
293 public boolean addMaxAgeTerm(int maxAgeInYears, boolean addToSubs) {
294 try {
295 return sidDoRequest(addMaxAgeTerm_req)
296 .addUintParm(1, maxAgeInYears)
297 .addBoolParm(2, addToSubs)
298 .endRequest().getBoolParm(1, true);
299 } catch(IOException e) {
300 mSidRoot.sidOnFatalError(e);
301 return false
302 ;}
303 }
304 private final static byte[] addEmailTerm_req = {(byte) 90,(byte) 82,(byte) 1,(byte) 3};
305 /** Adds a search term against Contact.P_EMAILS property and pre-validates the value given in the email argument. * @param email e-mail addres to search for.
306 * @param addToSubs This argument enables you to group conditions. See ContactSearch class details for more information.
307 * @return valid Returns false if the value in email property did not look like a valid email address.
308 */
309 public boolean addEmailTerm(String email, boolean addToSubs) {
310 try {
311 return sidDoRequest(addEmailTerm_req)
312 .addStringParm(1, email)
313 .addBoolParm(2, addToSubs)
314 .endRequest().getBoolParm(1, true);
315 } catch(IOException e) {
316 mSidRoot.sidOnFatalError(e);
317 return false
318 ;}
319 }
320 private final static byte[] addLanguageTerm_req = {(byte) 90,(byte) 82,(byte) 1,(byte) 4};
321 /**
322 * addLanguageTerm
323 * @param language
324 * @param addToSubs
325 * @return valid
326 */
327 public boolean addLanguageTerm(String language, boolean addToSubs) {
328 try {
329 return sidDoRequest(addLanguageTerm_req)
330 .addStringParm(1, language)
331 .addBoolParm(2, addToSubs)
332 .endRequest().getBoolParm(1, true);
333 } catch(IOException e) {
334 mSidRoot.sidOnFatalError(e);
335 return false
336 ;}
337 }
338 private final static byte[] addStrTerm_req = {(byte) 90,(byte) 82,(byte) 1,(byte) 5};
339 /** Adds a string search term to a custom contact search object. * @param prop Following Contact class string propkeys can be used for Contact search:
340 * - Contact.P_SKYPENAME
341 * - Contact.P_FULLNAME
342 * - Contact.P_LANGUAGES
343 * - Contact.P_COUNTRY
344 * - Contact.P_PROVINCE
345 * - Contact.P_CITY
346 * - Contact.P_PHONE_HOME
347 * - Contact.P_PHONE_OFFICE
348 * - Contact.P_PHONE_MOBILE
349 * - Contact.P_EMAILS
350 * - Contact.P_HOMEPAGE
351 * - Contact.P_ABOUT
352 * 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.
353
354 * @param cond Search condition (ContactSearch.CONDITION)
355 * @param value Value to match against.
356 * @param addToSubs This argument enables you to group conditions. See ContactSearch class details for more information.
357 * @return valid Returns true if the ContactSearch term-set remains valid after adding this term.
358 */
359 public boolean addStrTerm(int prop, Condition cond, String value, boolean addToSubs) {
360 try {
361 return sidDoRequest(addStrTerm_req)
362 .addEnumParm(1, prop)
363 .addEnumParm(2, cond)
364 .addStringParm(3, value)
365 .addBoolParm(4, addToSubs)
366 .endRequest().getBoolParm(1, true);
367 } catch(IOException e) {
368 mSidRoot.sidOnFatalError(e);
369 return false
370 ;}
371 }
372 private final static byte[] addIntTerm_req = {(byte) 90,(byte) 82,(byte) 1,(byte) 6};
373 /** 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. * @param prop Propkey to search for. Either Contact.P_BIRTHDAY or Contact.P_GENDER * @param cond Search condition (ContactSearch.CONDITION)
374 * @param value Value to match against.
375 * @param addToSubs This argument enables you to group conditions. See ContactSearch class details for more information.
376 * @return valid Returns true if the ContactSearch term-set remains valid after adding this term.
377 */
378 public boolean addIntTerm(int prop, Condition cond, int value, boolean addToSubs) {
379 try {
380 return sidDoRequest(addIntTerm_req)
381 .addEnumParm(1, prop)
382 .addEnumParm(2, cond)
383 .addUintParm(3, value)
384 .addBoolParm(4, addToSubs)
385 .endRequest().getBoolParm(1, true);
386 } catch(IOException e) {
387 mSidRoot.sidOnFatalError(e);
388 return false
389 ;}
390 }
391 private final static byte[] addOr_req = {(byte) 90,(byte) 82,(byte) 1,(byte) 7};
392 /** used to group terms (AddTerm(1), AddTerm(2), Or(), AddTerm(3), AddTerm(4), etc) */
393 public void addOr() {
394 try {
395 sidDoRequest(addOr_req)
396 .endOneWay();
397 } catch(IOException e) {
398 mSidRoot.sidOnFatalError(e);
399 }
400 }
401 private final static byte[] isValid_req = {(byte) 90,(byte) 82,(byte) 1,(byte) 8};
402 /** checks that terms list is non-empty and does not contain unsupported keys * @return result
403 */
404 public boolean isValid() {
405 try {
406 return sidDoRequest(isValid_req)
407 .endRequest().getBoolParm(1, true);
408 } catch(IOException e) {
409 mSidRoot.sidOnFatalError(e);
410 return false
411 ;}
412 }
413 private final static byte[] submit_req = {(byte) 90,(byte) 82,(byte) 1,(byte) 9};
414 /** launch search */
415 public void submit() {
416 try {
417 sidDoRequest(submit_req)
418 .endOneWay();
419 } catch(IOException e) {
420 mSidRoot.sidOnFatalError(e);
421 }
422 }
423 private final static byte[] extend_req = {(byte) 90,(byte) 82,(byte) 1,(byte) 10};
424 /** extend if search is EXTENDABLE */
425 public void extend() {
426 try {
427 sidDoRequest(extend_req)
428 .endOneWay();
429 } catch(IOException e) {
430 mSidRoot.sidOnFatalError(e);
431 }
432 }
433 private final static byte[] release_req = {(byte) 90,(byte) 82,(byte) 1,(byte) 12};
434 /** releases results that are not referenced from elsewhere */
435 public void release() {
436 try {
437 sidDoRequest(release_req)
438 .endOneWay();
439 } catch(IOException e) {
440 mSidRoot.sidOnFatalError(e);
441 }
442 }
443 private final static byte[] getResults_req = {(byte) 90,(byte) 82,(byte) 1,(byte) 11};
444 /** result list is dynamically updated * @param from
445 * @param count
446 * @return contacts
447 */
448 public Contact[] getResults(int from, int count) {
449 try {
450 return (Contact[]) sidDoRequest(getResults_req)
451 .addUintParm(1, from)
452 .addUintParm(2, count, Integer.MIN_VALUE)
453 .endRequest().getObjectListParm(1, 2, true);
454 } catch(IOException e) {
455 mSidRoot.sidOnFatalError(e);
456 return null
457 ;}
458 }
459 /***
460 * generic multiget of a list of Property
461 * @param requested the list of requested properties of ContactSearch
462 * @return SidGetResponding
463 */
464 public SidGetResponding sidMultiGet(Property[] requested) {
465 return super.sidMultiGet(requested);
466 }
467 /***
468 * generic multiget of list of Property for a list of ContactSearch
469 * @param requested the list of requested properties
470 * @return SidGetResponding[] can be casted to (ContactSearch[]) if all properties are cached
471 */
472 static public SidGetResponding[] sidMultiGet(Property[] requested, ContactSearch[] objects) {
473 return SidObject.sidMultiGet(requested, objects);
474 }
475 public Status getContactSearchStatus() {
476 synchronized(this) {
477 if ((mSidCached & 0x1) != 0)
478 return mContactSearchStatus;
479 }
480 return (Status) sidRequestEnumProperty(Property.P_CONTACT_SEARCH_STATUS);
481 }
482 public EnumConverting sidGetEnumProperty(final PropertyEnumConverting prop) {
483 assert(prop.getId() == 200);
484 return mContactSearchStatus;
485 }
486 public String getPropertyAsString(final int prop) {
487 switch (prop) {
488 case 200: return getContactSearchStatus().toString();
489 }
490 return "<unkown>";
491 }
492 public String getPropertyAsString(final Property prop) {
493 return getPropertyAsString(prop.getId());
494 }
495 protected void sidOnChangedProperty(final int propertyId, final int value, final String svalue) {
496 final Property property = Property.get(propertyId);
497 if (property == Property.P_UNKNOWN) return;
498 final int idx = property.getIdx();
499 if (idx != 0) {
500 int bit = 1<<((idx-1)%32);
501 synchronized (this) {
502 mSidCached|=bit;
503 switch (propertyId) {
504 case 200: mContactSearchStatus = Status.get(value); break;
505 default: mSidCached|=bit; break;
506 }
507 }
508 }
509 ContactSearchListener listener = ((Skype) mSidRoot).getContactSearchListener();
510 if (listener != null)
511 listener.onPropertyChange(this, property, value, svalue);
512 }
513 public void sidSetProperty(final PropertyEnumConverting prop, final int newValue) {
514 final int propId = prop.getId();
515 assert(propId == 200);
516 mSidCached |= 0x1;
517 mContactSearchStatus= Status.get(newValue);
518 }
519 public Status mContactSearchStatus;
520 public int moduleId() {
521 return 1;
522 }
523
524 public ContactSearch(final int oid, final SidRoot root) {
525 super(oid, root, 1);
526 }
527 }