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    }