Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
14792 manas 1
/**
2
 * Copyright 2010-present Facebook.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *    http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
 
17
package com.facebook;
18
 
19
import android.content.Context;
20
import android.graphics.Bitmap;
21
import android.location.Location;
22
import android.net.Uri;
23
import android.os.*;
24
import android.text.TextUtils;
25
import android.util.Log;
26
import android.util.Pair;
27
import com.facebook.internal.*;
28
import com.facebook.model.*;
29
import org.json.JSONArray;
30
import org.json.JSONException;
31
import org.json.JSONObject;
32
 
33
import java.io.*;
34
import java.net.HttpURLConnection;
35
import java.net.MalformedURLException;
36
import java.net.URL;
37
import java.net.URLEncoder;
38
import java.text.SimpleDateFormat;
39
import java.util.*;
40
import java.util.Map.Entry;
41
import java.util.regex.Matcher;
42
import java.util.regex.Pattern;
43
 
44
/**
45
 * A single request to be sent to the Facebook Platform through the <a
46
 * href="https://developers.facebook.com/docs/reference/api/">Graph API</a>. The Request class provides functionality
47
 * relating to serializing and deserializing requests and responses, making calls in batches (with a single round-trip
48
 * to the service) and making calls asynchronously.
49
 *
50
 * The particular service endpoint that a request targets is determined by a graph path (see the
51
 * {@link #setGraphPath(String) setGraphPath} method).
52
 *
53
 * A Request can be executed either anonymously or representing an authenticated user. In the former case, no Session
54
 * needs to be specified, while in the latter, a Session that is in an opened state must be provided. If requests are
55
 * executed in a batch, a Facebook application ID must be associated with the batch, either by supplying a Session for
56
 * at least one of the requests in the batch (the first one found in the batch will be used) or by calling the
57
 * {@link #setDefaultBatchApplicationId(String) setDefaultBatchApplicationId} method.
58
 *
59
 * After completion of a request, its Session, if any, will be checked to determine if its Facebook access token needs
60
 * to be extended; if so, a request to extend it will be issued in the background.
61
 */
62
public class Request {
63
    /**
64
     * The maximum number of requests that can be submitted in a single batch. This limit is enforced on the service
65
     * side by the Facebook platform, not by the Request class.
66
     */
67
    public static final int MAXIMUM_BATCH_SIZE = 50;
68
 
69
    public static final String TAG = Request.class.getSimpleName();
70
 
71
    private static final String ME = "me";
72
    private static final String MY_FRIENDS = "me/friends";
73
    private static final String MY_PHOTOS = "me/photos";
74
    private static final String MY_VIDEOS = "me/videos";
75
    private static final String VIDEOS_SUFFIX = "/videos";
76
    private static final String SEARCH = "search";
77
    private static final String MY_FEED = "me/feed";
78
    private static final String MY_STAGING_RESOURCES = "me/staging_resources";
79
    private static final String MY_OBJECTS_FORMAT = "me/objects/%s";
80
    private static final String MY_ACTION_FORMAT = "me/%s";
81
 
82
    private static final String USER_AGENT_BASE = "FBAndroidSDK";
83
    private static final String USER_AGENT_HEADER = "User-Agent";
84
    private static final String CONTENT_TYPE_HEADER = "Content-Type";
85
    private static final String ACCEPT_LANGUAGE_HEADER = "Accept-Language";
86
 
87
    // Parameter names/values
88
    private static final String PICTURE_PARAM = "picture";
89
    private static final String FORMAT_PARAM = "format";
90
    private static final String FORMAT_JSON = "json";
91
    private static final String SDK_PARAM = "sdk";
92
    private static final String SDK_ANDROID = "android";
93
    private static final String ACCESS_TOKEN_PARAM = "access_token";
94
    private static final String BATCH_ENTRY_NAME_PARAM = "name";
95
    private static final String BATCH_ENTRY_OMIT_RESPONSE_ON_SUCCESS_PARAM = "omit_response_on_success";
96
    private static final String BATCH_ENTRY_DEPENDS_ON_PARAM = "depends_on";
97
    private static final String BATCH_APP_ID_PARAM = "batch_app_id";
98
    private static final String BATCH_RELATIVE_URL_PARAM = "relative_url";
99
    private static final String BATCH_BODY_PARAM = "body";
100
    private static final String BATCH_METHOD_PARAM = "method";
101
    private static final String BATCH_PARAM = "batch";
102
    private static final String ATTACHMENT_FILENAME_PREFIX = "file";
103
    private static final String ATTACHED_FILES_PARAM = "attached_files";
104
    private static final String ISO_8601_FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ssZ";
105
    private static final String STAGING_PARAM = "file";
106
    private static final String OBJECT_PARAM = "object";
107
 
108
    private static final String MIME_BOUNDARY = "3i2ndDfv2rTHiSisAbouNdArYfORhtTPEefj3q2f";
109
 
110
    private static String defaultBatchApplicationId;
111
 
112
    // Group 1 in the pattern is the path without the version info
113
    private static Pattern versionPattern = Pattern.compile("^/?v\\d+\\.\\d+/(.*)");
114
 
115
    private Session session;
116
    private HttpMethod httpMethod;
117
    private String graphPath;
118
    private GraphObject graphObject;
119
    private String batchEntryName;
120
    private String batchEntryDependsOn;
121
    private boolean batchEntryOmitResultOnSuccess = true;
122
    private Bundle parameters;
123
    private Callback callback;
124
    private String overriddenURL;
125
    private Object tag;
126
    private String version;
127
    private boolean skipClientToken = false;
128
 
129
    /**
130
     * Constructs a request without a session, graph path, or any other parameters.
131
     */
132
    public Request() {
133
        this(null, null, null, null, null);
134
    }
135
 
136
    /**
137
     * Constructs a request with a Session to retrieve a particular graph path. A Session need not be provided, in which
138
     * case the request is sent without an access token and thus is not executed in the context of any particular user.
139
     * Only certain graph requests can be expected to succeed in this case. If a Session is provided, it must be in an
140
     * opened state or the request will fail.
141
     *
142
     * @param session
143
     *            the Session to use, or null
144
     * @param graphPath
145
     *            the graph path to retrieve
146
     */
147
    public Request(Session session, String graphPath) {
148
        this(session, graphPath, null, null, null);
149
    }
150
 
151
    /**
152
     * Constructs a request with a specific Session, graph path, parameters, and HTTP method. A Session need not be
153
     * provided, in which case the request is sent without an access token and thus is not executed in the context of
154
     * any particular user. Only certain graph requests can be expected to succeed in this case. If a Session is
155
     * provided, it must be in an opened state or the request will fail.
156
     *
157
     * Depending on the httpMethod parameter, the object at the graph path may be retrieved, created, or deleted.
158
     *
159
     * @param session
160
     *            the Session to use, or null
161
     * @param graphPath
162
     *            the graph path to retrieve, create, or delete
163
     * @param parameters
164
     *            additional parameters to pass along with the Graph API request; parameters must be Strings, Numbers,
165
     *            Bitmaps, Dates, or Byte arrays.
166
     * @param httpMethod
167
     *            the {@link HttpMethod} to use for the request, or null for default (HttpMethod.GET)
168
     */
169
    public Request(Session session, String graphPath, Bundle parameters, HttpMethod httpMethod) {
170
        this(session, graphPath, parameters, httpMethod, null);
171
    }
172
 
173
    /**
174
     * Constructs a request with a specific Session, graph path, parameters, and HTTP method. A Session need not be
175
     * provided, in which case the request is sent without an access token and thus is not executed in the context of
176
     * any particular user. Only certain graph requests can be expected to succeed in this case. If a Session is
177
     * provided, it must be in an opened state or the request will fail.
178
     *
179
     * Depending on the httpMethod parameter, the object at the graph path may be retrieved, created, or deleted.
180
     *
181
     * @param session
182
     *            the Session to use, or null
183
     * @param graphPath
184
     *            the graph path to retrieve, create, or delete
185
     * @param parameters
186
     *            additional parameters to pass along with the Graph API request; parameters must be Strings, Numbers,
187
     *            Bitmaps, Dates, or Byte arrays.
188
     * @param httpMethod
189
     *            the {@link HttpMethod} to use for the request, or null for default (HttpMethod.GET)
190
     * @param callback
191
     *            a callback that will be called when the request is completed to handle success or error conditions
192
     */
193
    public Request(Session session, String graphPath, Bundle parameters, HttpMethod httpMethod, Callback callback) {
194
        this(session, graphPath, parameters, httpMethod, callback, null);
195
    }
196
 
197
    /**
198
     * Constructs a request with a specific Session, graph path, parameters, and HTTP method. A Session need not be
199
     * provided, in which case the request is sent without an access token and thus is not executed in the context of
200
     * any particular user. Only certain graph requests can be expected to succeed in this case. If a Session is
201
     * provided, it must be in an opened state or the request will fail.
202
     *
203
     * Depending on the httpMethod parameter, the object at the graph path may be retrieved, created, or deleted.
204
     *
205
     * @param session
206
     *            the Session to use, or null
207
     * @param graphPath
208
     *            the graph path to retrieve, create, or delete
209
     * @param parameters
210
     *            additional parameters to pass along with the Graph API request; parameters must be Strings, Numbers,
211
     *            Bitmaps, Dates, or Byte arrays.
212
     * @param httpMethod
213
     *            the {@link HttpMethod} to use for the request, or null for default (HttpMethod.GET)
214
     * @param callback
215
     *            a callback that will be called when the request is completed to handle success or error conditions
216
     * @param version
217
     *            the version of the Graph API
218
     */
219
    public Request(Session session, String graphPath, Bundle parameters, HttpMethod httpMethod, Callback callback, String version) {
220
        this.session = session;
221
        this.graphPath = graphPath;
222
        this.callback = callback;
223
        this.version = version;
224
 
225
        setHttpMethod(httpMethod);
226
 
227
        if (parameters != null) {
228
            this.parameters = new Bundle(parameters);
229
        } else {
230
            this.parameters = new Bundle();
231
        }
232
 
233
        if (this.version == null) {
234
            this.version = ServerProtocol.getAPIVersion();
235
        }
236
    }
237
 
238
    Request(Session session, URL overriddenURL) {
239
        this.session = session;
240
        this.overriddenURL = overriddenURL.toString();
241
 
242
        setHttpMethod(HttpMethod.GET);
243
 
244
        this.parameters = new Bundle();
245
    }
246
 
247
    /**
248
     * Creates a new Request configured to post a GraphObject to a particular graph path, to either create or update the
249
     * object at that path.
250
     *
251
     * @param session
252
     *            the Session to use, or null; if non-null, the session must be in an opened state
253
     * @param graphPath
254
     *            the graph path to retrieve, create, or delete
255
     * @param graphObject
256
     *            the GraphObject to create or update
257
     * @param callback
258
     *            a callback that will be called when the request is completed to handle success or error conditions
259
     * @return a Request that is ready to execute
260
     */
261
    public static Request newPostRequest(Session session, String graphPath, GraphObject graphObject, Callback callback) {
262
        Request request = new Request(session, graphPath, null, HttpMethod.POST , callback);
263
        request.setGraphObject(graphObject);
264
        return request;
265
    }
266
 
267
    /**
268
     * Creates a new Request configured to retrieve a user's own profile.
269
     *
270
     * @param session
271
     *            the Session to use, or null; if non-null, the session must be in an opened state
272
     * @param callback
273
     *            a callback that will be called when the request is completed to handle success or error conditions
274
     * @return a Request that is ready to execute
275
     */
276
    public static Request newMeRequest(Session session, final GraphUserCallback callback) {
277
        Callback wrapper = new Callback() {
278
            @Override
279
            public void onCompleted(Response response) {
280
                if (callback != null) {
281
                    callback.onCompleted(response.getGraphObjectAs(GraphUser.class), response);
282
                }
283
            }
284
        };
285
        return new Request(session, ME, null, null, wrapper);
286
    }
287
 
288
    /**
289
     * Creates a new Request configured to retrieve a user's friend list.
290
     *
291
     * @param session
292
     *            the Session to use, or null; if non-null, the session must be in an opened state
293
     * @param callback
294
     *            a callback that will be called when the request is completed to handle success or error conditions
295
     * @return a Request that is ready to execute
296
     */
297
    public static Request newMyFriendsRequest(Session session, final GraphUserListCallback callback) {
298
        Callback wrapper = new Callback() {
299
            @Override
300
            public void onCompleted(Response response) {
301
                if (callback != null) {
302
                    callback.onCompleted(typedListFromResponse(response, GraphUser.class), response);
303
                }
304
            }
305
        };
306
        return new Request(session, MY_FRIENDS, null, null, wrapper);
307
    }
308
 
309
    /**
310
     * Creates a new Request configured to upload a photo to the user's default photo album.
311
     *
312
     * @param session
313
     *            the Session to use, or null; if non-null, the session must be in an opened state
314
     * @param image
315
     *            the image to upload
316
     * @param callback
317
     *            a callback that will be called when the request is completed to handle success or error conditions
318
     * @return a Request that is ready to execute
319
     */
320
    public static Request newUploadPhotoRequest(Session session, Bitmap image, Callback callback) {
321
        Bundle parameters = new Bundle(1);
322
        parameters.putParcelable(PICTURE_PARAM, image);
323
 
324
        return new Request(session, MY_PHOTOS, parameters, HttpMethod.POST, callback);
325
    }
326
 
327
    /**
328
     * Creates a new Request configured to upload a photo to the user's default photo album. The photo
329
     * will be read from the specified stream.
330
     *
331
     * @param session  the Session to use, or null; if non-null, the session must be in an opened state
332
     * @param file     the file containing the photo to upload
333
     * @param callback a callback that will be called when the request is completed to handle success or error conditions
334
     * @return a Request that is ready to execute
335
     */
336
    public static Request newUploadPhotoRequest(Session session, File file,
337
            Callback callback) throws FileNotFoundException {
338
        ParcelFileDescriptor descriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
339
        Bundle parameters = new Bundle(1);
340
        parameters.putParcelable(PICTURE_PARAM, descriptor);
341
 
342
        return new Request(session, MY_PHOTOS, parameters, HttpMethod.POST, callback);
343
    }
344
 
345
    /**
346
     * Creates a new Request configured to upload a photo to the user's default photo album. The photo
347
     * will be read from the specified file descriptor.
348
     *
349
     * @param session  the Session to use, or null; if non-null, the session must be in an opened state
350
     * @param file     the file to upload
351
     * @param callback a callback that will be called when the request is completed to handle success or error conditions
352
     * @return a Request that is ready to execute
353
     */
354
    public static Request newUploadVideoRequest(Session session, File file,
355
            Callback callback) throws FileNotFoundException {
356
        ParcelFileDescriptor descriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
357
        Bundle parameters = new Bundle(1);
358
        parameters.putParcelable(file.getName(), descriptor);
359
 
360
        return new Request(session, MY_VIDEOS, parameters, HttpMethod.POST, callback);
361
    }
362
 
363
    /**
364
     * Creates a new Request configured to retrieve a particular graph path.
365
     *
366
     * @param session
367
     *            the Session to use, or null; if non-null, the session must be in an opened state
368
     * @param graphPath
369
     *            the graph path to retrieve
370
     * @param callback
371
     *            a callback that will be called when the request is completed to handle success or error conditions
372
     * @return a Request that is ready to execute
373
     */
374
    public static Request newGraphPathRequest(Session session, String graphPath, Callback callback) {
375
        return new Request(session, graphPath, null, null, callback);
376
    }
377
 
378
    /**
379
     * Creates a new Request that is configured to perform a search for places near a specified location via the Graph
380
     * API. At least one of location or searchText must be specified.
381
     *
382
     * @param session
383
     *            the Session to use, or null; if non-null, the session must be in an opened state
384
     * @param location
385
     *            the location around which to search; only the latitude and longitude components of the location are
386
     *            meaningful
387
     * @param radiusInMeters
388
     *            the radius around the location to search, specified in meters; this is ignored if
389
     *            no location is specified
390
     * @param resultsLimit
391
     *            the maximum number of results to return
392
     * @param searchText
393
     *            optional text to search for as part of the name or type of an object
394
     * @param callback
395
     *            a callback that will be called when the request is completed to handle success or error conditions
396
     * @return a Request that is ready to execute
397
     *
398
     * @throws FacebookException If neither location nor searchText is specified
399
     */
400
    public static Request newPlacesSearchRequest(Session session, Location location, int radiusInMeters,
401
            int resultsLimit, String searchText, final GraphPlaceListCallback callback) {
402
        if (location == null && Utility.isNullOrEmpty(searchText)) {
403
            throw new FacebookException("Either location or searchText must be specified.");
404
        }
405
 
406
        Bundle parameters = new Bundle(5);
407
        parameters.putString("type", "place");
408
        parameters.putInt("limit", resultsLimit);
409
        if (location != null) {
410
            parameters.putString("center",
411
                    String.format(Locale.US, "%f,%f", location.getLatitude(), location.getLongitude()));
412
            parameters.putInt("distance", radiusInMeters);
413
        }
414
        if (!Utility.isNullOrEmpty(searchText)) {
415
            parameters.putString("q", searchText);
416
        }
417
 
418
        Callback wrapper = new Callback() {
419
            @Override
420
            public void onCompleted(Response response) {
421
                if (callback != null) {
422
                    callback.onCompleted(typedListFromResponse(response, GraphPlace.class), response);
423
                }
424
            }
425
        };
426
 
427
        return new Request(session, SEARCH, parameters, HttpMethod.GET, wrapper);
428
    }
429
 
430
    /**
431
     * Creates a new Request configured to post a status update to a user's feed.
432
     *
433
     * @param session
434
     *            the Session to use, or null; if non-null, the session must be in an opened state
435
     * @param message
436
     *            the text of the status update
437
     * @param callback
438
     *            a callback that will be called when the request is completed to handle success or error conditions
439
     * @return a Request that is ready to execute
440
     */
441
    public static Request newStatusUpdateRequest(Session session, String message, Callback callback) {
442
        return newStatusUpdateRequest(session, message, (String)null, null, callback);
443
    }
444
 
445
    /**
446
     * Creates a new Request configured to post a status update to a user's feed.
447
     *
448
     * @param session
449
     *            the Session to use, or null; if non-null, the session must be in an opened state
450
     * @param message
451
     *            the text of the status update
452
     * @param placeId
453
     *            an optional place id to associate with the post
454
     * @param tagIds
455
     *            an optional list of user ids to tag in the post
456
     * @param callback
457
     *            a callback that will be called when the request is completed to handle success or error conditions
458
     * @return a Request that is ready to execute
459
     */
460
    private static Request newStatusUpdateRequest(Session session, String message, String placeId, List<String> tagIds,
461
            Callback callback) {
462
 
463
        Bundle parameters = new Bundle();
464
        parameters.putString("message", message);
465
 
466
        if (placeId != null) {
467
            parameters.putString("place", placeId);
468
        }
469
 
470
        if (tagIds != null && tagIds.size() > 0) {
471
            String tags = TextUtils.join(",", tagIds);
472
            parameters.putString("tags", tags);
473
        }
474
 
475
        return new Request(session, MY_FEED, parameters, HttpMethod.POST, callback);
476
    }
477
 
478
    /**
479
     * Creates a new Request configured to post a status update to a user's feed.
480
     *
481
     * @param session
482
     *            the Session to use, or null; if non-null, the session must be in an opened state
483
     * @param message
484
     *            the text of the status update
485
     * @param place
486
     *            an optional place to associate with the post
487
     * @param tags
488
     *            an optional list of users to tag in the post
489
     * @param callback
490
     *            a callback that will be called when the request is completed to handle success or error conditions
491
     * @return a Request that is ready to execute
492
     */
493
    public static Request newStatusUpdateRequest(Session session, String message, GraphPlace place,
494
            List<GraphUser> tags, Callback callback) {
495
 
496
        List<String> tagIds = null;
497
        if (tags != null) {
498
            tagIds = new ArrayList<String>(tags.size());
499
            for (GraphUser tag: tags) {
500
                tagIds.add(tag.getId());
501
            }
502
        }
503
        String placeId = place == null ? null : place.getId();
504
        return newStatusUpdateRequest(session, message, placeId, tagIds, callback);
505
    }
506
 
507
    /**
508
     * Creates a new Request configured to retrieve an App User ID for the app's Facebook user.  Callers
509
     * will send this ID back to their own servers, collect up a set to create a Facebook Custom Audience with,
510
     * and then use the resultant Custom Audience to target ads.
511
     * <p/>
512
     * The GraphObject in the response will include an "custom_audience_third_party_id" property, with the value
513
     * being the ID retrieved.  This ID is an encrypted encoding of the Facebook user's ID and the
514
     * invoking Facebook app ID.  Multiple calls with the same user will return different IDs, thus these IDs cannot be
515
     * used to correlate behavior across devices or applications, and are only meaningful when sent back to Facebook
516
     * for creating Custom Audiences.
517
     * <p/>
518
     * The ID retrieved represents the Facebook user identified in the following way: if the specified session
519
     * (or activeSession if the specified session is `null`) is open, the ID will represent the user associated with
520
     * the activeSession; otherwise the ID will represent the user logged into the native Facebook app on the device.
521
     * A `null` ID will be provided into the callback if a) there is no native Facebook app, b) no one is logged into
522
     * it, or c) the app has previously called
523
     * {@link Settings#setLimitEventAndDataUsage(android.content.Context, boolean)} with `true` for this user.
524
     * <b>You must call this method from a background thread for it to work properly.</b>
525
     *
526
     * @param session
527
     *            the Session to issue the Request on, or null; if non-null, the session must be in an opened state.
528
     *            If there is no logged-in Facebook user, null is the expected choice.
529
     * @param context
530
     *            the Application context from which the app ID will be pulled, and from which the 'attribution ID'
531
     *            for the Facebook user is determined.  If there has been no app ID set, an exception will be thrown.
532
     * @param callback
533
     *            a callback that will be called when the request is completed to handle success or error conditions.
534
     *            The GraphObject in the Response will contain a "custom_audience_third_party_id" property that
535
     *            represents the user as described above.
536
     * @return a Request that is ready to execute
537
     */
538
    public static Request newCustomAudienceThirdPartyIdRequest(Session session, Context context, Callback callback) {
539
        return newCustomAudienceThirdPartyIdRequest(session, context, null, callback);
540
    }
541
 
542
    /**
543
     * Creates a new Request configured to retrieve an App User ID for the app's Facebook user.  Callers
544
     * will send this ID back to their own servers, collect up a set to create a Facebook Custom Audience with,
545
     * and then use the resultant Custom Audience to target ads.
546
     * <p/>
547
     * The GraphObject in the response will include an "custom_audience_third_party_id" property, with the value
548
     * being the ID retrieved.  This ID is an encrypted encoding of the Facebook user's ID and the
549
     * invoking Facebook app ID.  Multiple calls with the same user will return different IDs, thus these IDs cannot be
550
     * used to correlate behavior across devices or applications, and are only meaningful when sent back to Facebook
551
     * for creating Custom Audiences.
552
     * <p/>
553
     * The ID retrieved represents the Facebook user identified in the following way: if the specified session
554
     * (or activeSession if the specified session is `null`) is open, the ID will represent the user associated with
555
     * the activeSession; otherwise the ID will represent the user logged into the native Facebook app on the device.
556
     * A `null` ID will be provided into the callback if a) there is no native Facebook app, b) no one is logged into
557
     * it, or c) the app has previously called
558
     * {@link Settings#setLimitEventAndDataUsage(android.content.Context, boolean)} ;} with `true` for this user.
559
     * <b>You must call this method from a background thread for it to work properly.</b>
560
     *
561
     * @param session
562
     *            the Session to issue the Request on, or null; if non-null, the session must be in an opened state.
563
     *            If there is no logged-in Facebook user, null is the expected choice.
564
     * @param context
565
     *            the Application context from which the app ID will be pulled, and from which the 'attribution ID'
566
     *            for the Facebook user is determined.  If there has been no app ID set, an exception will be thrown.
567
     * @param applicationId
568
     *            explicitly specified Facebook App ID.  If null, and there's a valid session, then the application ID
569
     *            from the session will be used, otherwise the application ID from metadata will be used.
570
     * @param callback
571
     *            a callback that will be called when the request is completed to handle success or error conditions.
572
     *            The GraphObject in the Response will contain a "custom_audience_third_party_id" property that
573
     *            represents the user as described above.
574
     * @return a Request that is ready to execute
575
     */
576
    public static Request newCustomAudienceThirdPartyIdRequest(Session session,
577
            Context context, String applicationId, Callback callback) {
578
 
579
        // if provided session or activeSession is opened, use it.
580
        if (session == null) {
581
            session = Session.getActiveSession();
582
        }
583
 
584
        if (session != null && !session.isOpened()) {
585
            session = null;
586
        }
587
 
588
        if (applicationId == null) {
589
            if (session != null) {
590
                applicationId = session.getApplicationId();
591
            } else {
592
                applicationId = Utility.getMetadataApplicationId(context);
593
            }
594
        }
595
 
596
        if (applicationId == null) {
597
            throw new FacebookException("Facebook App ID cannot be determined");
598
        }
599
 
600
        String endpoint = applicationId + "/custom_audience_third_party_id";
601
        AttributionIdentifiers attributionIdentifiers = AttributionIdentifiers.getAttributionIdentifiers(context);
602
        Bundle parameters = new Bundle();
603
 
604
        if (session == null) {
605
            // Only use the attributionID if we don't have an open session.  If we do have an open session, then
606
            // the user token will be used to identify the user, and is more reliable than the attributionID.
607
            String udid = attributionIdentifiers.getAttributionId() != null
608
                ? attributionIdentifiers.getAttributionId()
609
                : attributionIdentifiers.getAndroidAdvertiserId();
610
            if (attributionIdentifiers.getAttributionId() != null) {
611
                parameters.putString("udid", udid);
612
            }
613
        }
614
 
615
        // Server will choose to not provide the App User ID in the event that event usage has been limited for
616
        // this user for this app.
617
        if (Settings.getLimitEventAndDataUsage(context) || attributionIdentifiers.isTrackingLimited()) {
618
            parameters.putString("limit_event_usage", "1");
619
        }
620
 
621
        return new Request(session, endpoint, parameters, HttpMethod.GET, callback);
622
    }
623
 
624
    /**
625
     * Creates a new Request configured to upload an image to create a staging resource. Staging resources
626
     * allow you to post binary data such as images, in preparation for a post of an Open Graph object or action
627
     * which references the image. The URI returned when uploading a staging resource may be passed as the image
628
     * property for an Open Graph object or action.
629
     *
630
     * @param session
631
     *            the Session to use, or null; if non-null, the session must be in an opened state
632
     * @param image
633
     *            the image to upload
634
     * @param callback
635
     *            a callback that will be called when the request is completed to handle success or error conditions
636
     * @return a Request that is ready to execute
637
     */
638
    public static Request newUploadStagingResourceWithImageRequest(Session session,
639
            Bitmap image, Callback callback) {
640
        Bundle parameters = new Bundle(1);
641
        parameters.putParcelable(STAGING_PARAM, image);
642
 
643
        return new Request(session, MY_STAGING_RESOURCES, parameters, HttpMethod.POST, callback);
644
    }
645
 
646
    /**
647
     * Creates a new Request configured to upload an image to create a staging resource. Staging resources
648
     * allow you to post binary data such as images, in preparation for a post of an Open Graph object or action
649
     * which references the image. The URI returned when uploading a staging resource may be passed as the image
650
     * property for an Open Graph object or action.
651
     *
652
     * @param session
653
     *            the Session to use, or null; if non-null, the session must be in an opened state
654
     * @param file
655
     *            the file containing the image to upload
656
     * @param callback
657
     *            a callback that will be called when the request is completed to handle success or error conditions
658
     * @return a Request that is ready to execute
659
     */
660
    public static Request newUploadStagingResourceWithImageRequest(Session session,
661
            File file, Callback callback) throws FileNotFoundException {
662
        ParcelFileDescriptor descriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
663
        ParcelFileDescriptorWithMimeType descriptorWithMimeType = new ParcelFileDescriptorWithMimeType(descriptor, "image/png");
664
        Bundle parameters = new Bundle(1);
665
        parameters.putParcelable(STAGING_PARAM, descriptorWithMimeType);
666
 
667
        return new Request(session, MY_STAGING_RESOURCES, parameters, HttpMethod.POST, callback);
668
    }
669
 
670
    /**
671
     * Creates a new Request configured to create a user owned Open Graph object.
672
     *
673
     * @param session
674
     *            the Session to use, or null; if non-null, the session must be in an opened state
675
     * @param openGraphObject
676
     *            the Open Graph object to create; must not be null, and must have a non-empty type and title
677
     * @param callback
678
     *            a callback that will be called when the request is completed to handle success or error conditions
679
     * @return a Request that is ready to execute
680
     */
681
    public static Request newPostOpenGraphObjectRequest(Session session,
682
            OpenGraphObject openGraphObject, Callback callback) {
683
        if (openGraphObject == null) {
684
            throw new FacebookException("openGraphObject cannot be null");
685
        }
686
        if (Utility.isNullOrEmpty(openGraphObject.getType())) {
687
            throw new FacebookException("openGraphObject must have non-null 'type' property");
688
        }
689
        if (Utility.isNullOrEmpty(openGraphObject.getTitle())) {
690
            throw new FacebookException("openGraphObject must have non-null 'title' property");
691
        }
692
 
693
        String path = String.format(MY_OBJECTS_FORMAT, openGraphObject.getType());
694
        Bundle bundle = new Bundle();
695
        bundle.putString(OBJECT_PARAM, openGraphObject.getInnerJSONObject().toString());
696
        return new Request(session, path, bundle, HttpMethod.POST, callback);
697
    }
698
 
699
    /**
700
     * Creates a new Request configured to create a user owned Open Graph object.
701
     *
702
     * @param session
703
     *            the Session to use, or null; if non-null, the session must be in an opened state
704
     * @param type
705
     *            the fully-specified Open Graph object type (e.g., my_app_namespace:my_object_name); must not be null
706
     * @param title
707
     *            the title of the Open Graph object; must not be null
708
     * @param imageUrl
709
     *            the link to an image to be associated with the Open Graph object; may be null
710
     * @param url
711
     *            the url to be associated with the Open Graph object; may be null
712
     * @param description
713
     *            the description to be associated with the object; may be null
714
     * @param objectProperties
715
     *            any additional type-specific properties for the Open Graph object; may be null
716
     * @param callback
717
     *            a callback that will be called when the request is completed to handle success or error conditions;
718
     *            may be null
719
     * @return a Request that is ready to execute
720
     */
721
    public static Request newPostOpenGraphObjectRequest(Session session, String type, String title, String imageUrl,
722
            String url, String description, GraphObject objectProperties, Callback callback) {
723
        OpenGraphObject openGraphObject = OpenGraphObject.Factory.createForPost(OpenGraphObject.class, type, title,
724
                imageUrl, url, description);
725
        if (objectProperties != null) {
726
            openGraphObject.setData(objectProperties);
727
        }
728
 
729
        return newPostOpenGraphObjectRequest(session, openGraphObject, callback);
730
    }
731
 
732
    /**
733
     * Creates a new Request configured to publish an Open Graph action.
734
     *
735
     * @param session
736
     *            the Session to use, or null; if non-null, the session must be in an opened state
737
     * @param openGraphAction
738
     *            the Open Graph object to create; must not be null, and must have a non-empty 'type'
739
     * @param callback
740
     *            a callback that will be called when the request is completed to handle success or error conditions
741
     * @return a Request that is ready to execute
742
     */
743
    public static Request newPostOpenGraphActionRequest(Session session, OpenGraphAction openGraphAction,
744
            Callback callback) {
745
        if (openGraphAction == null) {
746
            throw new FacebookException("openGraphAction cannot be null");
747
        }
748
        if (Utility.isNullOrEmpty(openGraphAction.getType())) {
749
            throw new FacebookException("openGraphAction must have non-null 'type' property");
750
        }
751
 
752
        String path = String.format(MY_ACTION_FORMAT, openGraphAction.getType());
753
        return newPostRequest(session, path, openGraphAction, callback);
754
    }
755
 
756
    /**
757
     * Creates a new Request configured to delete a resource through the Graph API.
758
     *
759
     * @param session
760
     *            the Session to use, or null; if non-null, the session must be in an opened state
761
     * @param id
762
     *            the id of the object to delete
763
     * @param callback
764
     *            a callback that will be called when the request is completed to handle success or error conditions
765
     * @return a Request that is ready to execute
766
     */
767
    public static Request newDeleteObjectRequest(Session session, String id, Callback callback) {
768
        return new Request(session, id, null, HttpMethod.DELETE, callback);
769
    }
770
 
771
    /**
772
     * Creates a new Request configured to update a user owned Open Graph object.
773
     *
774
     * @param session
775
     *            the Session to use, or null; if non-null, the session must be in an opened state
776
     * @param openGraphObject
777
     *            the Open Graph object to update, which must have a valid 'id' property
778
     * @param callback
779
     *            a callback that will be called when the request is completed to handle success or error conditions
780
     * @return a Request that is ready to execute
781
     */
782
    public static Request newUpdateOpenGraphObjectRequest(Session session, OpenGraphObject openGraphObject,
783
            Callback callback) {
784
        if (openGraphObject == null) {
785
            throw new FacebookException("openGraphObject cannot be null");
786
        }
787
 
788
        String path = openGraphObject.getId();
789
        if (path == null) {
790
            throw new FacebookException("openGraphObject must have an id");
791
        }
792
 
793
        Bundle bundle = new Bundle();
794
        bundle.putString(OBJECT_PARAM, openGraphObject.getInnerJSONObject().toString());
795
        return new Request(session, path, bundle, HttpMethod.POST, callback);
796
    }
797
 
798
    /**
799
     * Creates a new Request configured to update a user owned Open Graph object.
800
     *
801
     * @param session
802
     *            the Session to use, or null; if non-null, the session must be in an opened state
803
     * @param id
804
     *            the id of the Open Graph object
805
     * @param title
806
     *            the title of the Open Graph object
807
     * @param imageUrl
808
     *            the link to an image to be associated with the Open Graph object
809
     * @param url
810
     *            the url to be associated with the Open Graph object
811
     * @param description
812
     *            the description to be associated with the object
813
     * @param objectProperties
814
     *            any additional type-specific properties for the Open Graph object
815
     * @param callback
816
     *            a callback that will be called when the request is completed to handle success or error conditions
817
     * @return a Request that is ready to execute
818
     */
819
    public static Request newUpdateOpenGraphObjectRequest(Session session, String id, String title, String imageUrl,
820
            String url, String description, GraphObject objectProperties, Callback callback) {
821
        OpenGraphObject openGraphObject = OpenGraphObject.Factory.createForPost(OpenGraphObject.class, null, title,
822
                imageUrl, url, description);
823
        openGraphObject.setId(id);
824
        openGraphObject.setData(objectProperties);
825
 
826
        return newUpdateOpenGraphObjectRequest(session, openGraphObject, callback);
827
    }
828
 
829
    /**
830
     * Returns the GraphObject, if any, associated with this request.
831
     *
832
     * @return the GraphObject associated with this requeset, or null if there is none
833
     */
834
    public final GraphObject getGraphObject() {
835
        return this.graphObject;
836
    }
837
 
838
    /**
839
     * Sets the GraphObject associated with this request. This is meaningful only for POST requests.
840
     *
841
     * @param graphObject
842
     *            the GraphObject to upload along with this request
843
     */
844
    public final void setGraphObject(GraphObject graphObject) {
845
        this.graphObject = graphObject;
846
    }
847
 
848
    /**
849
     * Returns the graph path of this request, if any.
850
     *
851
     * @return the graph path of this request, or null if there is none
852
     */
853
    public final String getGraphPath() {
854
        return this.graphPath;
855
    }
856
 
857
    /**
858
     * Sets the graph path of this request.
859
     *
860
     * @param graphPath
861
     *            the graph path for this request
862
     */
863
    public final void setGraphPath(String graphPath) {
864
        this.graphPath = graphPath;
865
    }
866
 
867
    /**
868
     * Returns the {@link HttpMethod} to use for this request.
869
     *
870
     * @return the HttpMethod
871
     */
872
    public final HttpMethod getHttpMethod() {
873
        return this.httpMethod;
874
    }
875
 
876
    /**
877
     * Sets the {@link HttpMethod} to use for this request.
878
     *
879
     * @param httpMethod
880
     *            the HttpMethod, or null for the default (HttpMethod.GET).
881
     */
882
    public final void setHttpMethod(HttpMethod httpMethod) {
883
        if (overriddenURL != null && httpMethod != HttpMethod.GET) {
884
            throw new FacebookException("Can't change HTTP method on request with overridden URL.");
885
            }
886
        this.httpMethod = (httpMethod != null) ? httpMethod : HttpMethod.GET;
887
    }
888
 
889
    /**
890
     * Returns the version of the API that this request will use.  By default this is the current API at the time
891
     * the SDK is released.
892
     *
893
     * @return the version that this request will use
894
     */
895
    public final String getVersion() {
896
        return this.version;
897
    }
898
 
899
    /**
900
     * Set the version to use for this request.  By default the version will be the current API at the time the SDK
901
     * is released.  Only use this if you need to explicitly override.
902
     *
903
     * @param version The version to use.  Should look like "v2.0"
904
     */
905
    public final void setVersion(String version) {
906
        this.version = version;
907
    }
908
 
909
    /**
910
     * This is an internal function that is not meant to be used by developers.
911
     */
912
    public final void setSkipClientToken(boolean skipClientToken) {
913
        this.skipClientToken = skipClientToken;
914
    }
915
 
916
    /**
917
     * Returns the parameters for this request.
918
     *
919
     * @return the parameters
920
     */
921
    public final Bundle getParameters() {
922
        return this.parameters;
923
    }
924
 
925
    /**
926
     * Sets the parameters for this request.
927
     *
928
     * @param parameters
929
     *            the parameters
930
     */
931
    public final void setParameters(Bundle parameters) {
932
        this.parameters = parameters;
933
    }
934
 
935
    /**
936
     * Returns the Session associated with this request.
937
     *
938
     * @return the Session associated with this request, or null if none has been specified
939
     */
940
    public final Session getSession() {
941
        return this.session;
942
    }
943
 
944
    /**
945
     * Sets the Session to use for this request. The Session does not need to be opened at the time it is specified, but
946
     * it must be opened by the time the request is executed.
947
     *
948
     * @param session
949
     *            the Session to use for this request
950
     */
951
    public final void setSession(Session session) {
952
        this.session = session;
953
    }
954
 
955
    /**
956
     * Returns the name of this request's entry in a batched request.
957
     *
958
     * @return the name of this request's batch entry, or null if none has been specified
959
     */
960
    public final String getBatchEntryName() {
961
        return this.batchEntryName;
962
    }
963
 
964
    /**
965
     * Sets the name of this request's entry in a batched request. This value is only used if this request is submitted
966
     * as part of a batched request. It can be used to specified dependencies between requests. See <a
967
     * href="https://developers.facebook.com/docs/reference/api/batch/">Batch Requests</a> in the Graph API
968
     * documentation for more details.
969
     *
970
     * @param batchEntryName
971
     *            the name of this request's entry in a batched request, which must be unique within a particular batch
972
     *            of requests
973
     */
974
    public final void setBatchEntryName(String batchEntryName) {
975
        this.batchEntryName = batchEntryName;
976
    }
977
 
978
    /**
979
     * Returns the name of the request that this request entry explicitly depends on in a batched request.
980
     *
981
     * @return the name of this request's dependency, or null if none has been specified
982
     */
983
    public final String getBatchEntryDependsOn() {
984
        return this.batchEntryDependsOn;
985
    }
986
 
987
    /**
988
     * Sets the name of the request entry that this request explicitly depends on in a batched request. This value is
989
     * only used if this request is submitted as part of a batched request. It can be used to specified dependencies
990
     * between requests. See <a href="https://developers.facebook.com/docs/reference/api/batch/">Batch Requests</a> in
991
     * the Graph API documentation for more details.
992
     *
993
     * @param batchEntryDependsOn
994
     *            the name of the request entry that this entry depends on in a batched request
995
     */
996
    public final void setBatchEntryDependsOn(String batchEntryDependsOn) {
997
        this.batchEntryDependsOn = batchEntryDependsOn;
998
    }
999
 
1000
 
1001
    /**
1002
     * Returns whether or not this batch entry will return a response if it is successful. Only applies if another
1003
     * request entry in the batch specifies this entry as a dependency.
1004
     *
1005
     * @return the name of this request's dependency, or null if none has been specified
1006
     */
1007
    public final boolean getBatchEntryOmitResultOnSuccess() {
1008
        return this.batchEntryOmitResultOnSuccess;
1009
    }
1010
 
1011
    /**
1012
     * Sets whether or not this batch entry will return a response if it is successful. Only applies if another
1013
     * request entry in the batch specifies this entry as a dependency. See
1014
     * <a href="https://developers.facebook.com/docs/reference/api/batch/">Batch Requests</a> in the Graph API
1015
     * documentation for more details.
1016
     *
1017
     * @param batchEntryOmitResultOnSuccess
1018
     *            the name of the request entry that this entry depends on in a batched request
1019
     */
1020
    public final void setBatchEntryOmitResultOnSuccess(boolean batchEntryOmitResultOnSuccess) {
1021
        this.batchEntryOmitResultOnSuccess = batchEntryOmitResultOnSuccess;
1022
    }
1023
 
1024
    /**
1025
     * Gets the default Facebook application ID that will be used to submit batched requests if none of those requests
1026
     * specifies a Session. Batched requests require an application ID, so either at least one request in a batch must
1027
     * specify a Session or the application ID must be specified explicitly.
1028
     *
1029
     * @return the Facebook application ID to use for batched requests if none can be determined
1030
     */
1031
    public static final String getDefaultBatchApplicationId() {
1032
        return Request.defaultBatchApplicationId;
1033
    }
1034
 
1035
    /**
1036
     * Sets the default application ID that will be used to submit batched requests if none of those requests specifies
1037
     * a Session. Batched requests require an application ID, so either at least one request in a batch must specify a
1038
     * Session or the application ID must be specified explicitly.
1039
     *
1040
     * @param applicationId
1041
     *            the Facebook application ID to use for batched requests if none can be determined
1042
     */
1043
    public static final void setDefaultBatchApplicationId(String applicationId) {
1044
        defaultBatchApplicationId = applicationId;
1045
    }
1046
 
1047
    /**
1048
     * Returns the callback which will be called when the request finishes.
1049
     *
1050
     * @return the callback
1051
     */
1052
    public final Callback getCallback() {
1053
        return callback;
1054
    }
1055
 
1056
    /**
1057
     * Sets the callback which will be called when the request finishes.
1058
     *
1059
     * @param callback
1060
     *            the callback
1061
     */
1062
    public final void setCallback(Callback callback) {
1063
        this.callback = callback;
1064
    }
1065
 
1066
    /**
1067
     * Sets the tag on the request; this is an application-defined object that can be used to distinguish
1068
     * between different requests. Its value has no effect on the execution of the request.
1069
     *
1070
     * @param tag an object to serve as a tag, or null
1071
     */
1072
    public final void setTag(Object tag) {
1073
        this.tag = tag;
1074
    }
1075
 
1076
    /**
1077
     * Gets the tag on the request; this is an application-defined object that can be used to distinguish
1078
     * between different requests. Its value has no effect on the execution of the request.
1079
     *
1080
     * @return an object that serves as a tag, or null
1081
     */
1082
    public final Object getTag() {
1083
        return tag;
1084
    }
1085
 
1086
    /**
1087
     * Starts a new Request configured to post a GraphObject to a particular graph path, to either create or update the
1088
     * object at that path.
1089
     * <p/>
1090
     * This should only be called from the UI thread.
1091
     *
1092
     * This method is deprecated. Prefer to call Request.newPostRequest(...).executeAsync();
1093
     *
1094
     * @param session
1095
     *            the Session to use, or null; if non-null, the session must be in an opened state
1096
     * @param graphPath
1097
     *            the graph path to retrieve, create, or delete
1098
     * @param graphObject
1099
     *            the GraphObject to create or update
1100
     * @param callback
1101
     *            a callback that will be called when the request is completed to handle success or error conditions
1102
     * @return a RequestAsyncTask that is executing the request
1103
     */
1104
    @Deprecated
1105
    public static RequestAsyncTask executePostRequestAsync(Session session, String graphPath, GraphObject graphObject,
1106
            Callback callback) {
1107
        return newPostRequest(session, graphPath, graphObject, callback).executeAsync();
1108
    }
1109
 
1110
    /**
1111
     * Starts a new Request configured to retrieve a user's own profile.
1112
     * <p/>
1113
     * This should only be called from the UI thread.
1114
     *
1115
     * This method is deprecated. Prefer to call Request.newMeRequest(...).executeAsync();
1116
     *
1117
     * @param session
1118
     *            the Session to use, or null; if non-null, the session must be in an opened state
1119
     * @param callback
1120
     *            a callback that will be called when the request is completed to handle success or error conditions
1121
     * @return a RequestAsyncTask that is executing the request
1122
     */
1123
    @Deprecated
1124
    public static RequestAsyncTask executeMeRequestAsync(Session session, GraphUserCallback callback) {
1125
        return newMeRequest(session, callback).executeAsync();
1126
    }
1127
 
1128
    /**
1129
     * Starts a new Request configured to retrieve a user's friend list.
1130
     * <p/>
1131
     * This should only be called from the UI thread.
1132
     *
1133
     * This method is deprecated. Prefer to call Request.newMyFriendsRequest(...).executeAsync();
1134
     *
1135
     * @param session
1136
     *            the Session to use, or null; if non-null, the session must be in an opened state
1137
     * @param callback
1138
     *            a callback that will be called when the request is completed to handle success or error conditions
1139
     * @return a RequestAsyncTask that is executing the request
1140
     */
1141
    @Deprecated
1142
    public static RequestAsyncTask executeMyFriendsRequestAsync(Session session, GraphUserListCallback callback) {
1143
        return newMyFriendsRequest(session, callback).executeAsync();
1144
    }
1145
 
1146
    /**
1147
     * Starts a new Request configured to upload a photo to the user's default photo album.
1148
     * <p/>
1149
     * This should only be called from the UI thread.
1150
     *
1151
     * This method is deprecated. Prefer to call Request.newUploadPhotoRequest(...).executeAsync();
1152
     *
1153
     * @param session
1154
     *            the Session to use, or null; if non-null, the session must be in an opened state
1155
     * @param image
1156
     *            the image to upload
1157
     * @param callback
1158
     *            a callback that will be called when the request is completed to handle success or error conditions
1159
     * @return a RequestAsyncTask that is executing the request
1160
     */
1161
    @Deprecated
1162
    public static RequestAsyncTask executeUploadPhotoRequestAsync(Session session, Bitmap image, Callback callback) {
1163
        return newUploadPhotoRequest(session, image, callback).executeAsync();
1164
    }
1165
 
1166
    /**
1167
     * Starts a new Request configured to upload a photo to the user's default photo album. The photo
1168
     * will be read from the specified stream.
1169
     * <p/>
1170
     * This should only be called from the UI thread.
1171
     *
1172
     * This method is deprecated. Prefer to call Request.newUploadPhotoRequest(...).executeAsync();
1173
     *
1174
     * @param session  the Session to use, or null; if non-null, the session must be in an opened state
1175
     * @param file     the file containing the photo to upload
1176
     * @param callback a callback that will be called when the request is completed to handle success or error conditions
1177
     * @return a RequestAsyncTask that is executing the request
1178
     */
1179
    @Deprecated
1180
    public static RequestAsyncTask executeUploadPhotoRequestAsync(Session session, File file,
1181
            Callback callback) throws FileNotFoundException {
1182
        return newUploadPhotoRequest(session, file, callback).executeAsync();
1183
    }
1184
 
1185
    /**
1186
     * Starts a new Request configured to retrieve a particular graph path.
1187
     * <p/>
1188
     * This should only be called from the UI thread.
1189
     *
1190
     * This method is deprecated. Prefer to call Request.newGraphPathRequest(...).executeAsync();
1191
     *
1192
     * @param session
1193
     *            the Session to use, or null; if non-null, the session must be in an opened state
1194
     * @param graphPath
1195
     *            the graph path to retrieve
1196
     * @param callback
1197
     *            a callback that will be called when the request is completed to handle success or error conditions
1198
     * @return a RequestAsyncTask that is executing the request
1199
     */
1200
    @Deprecated
1201
    public static RequestAsyncTask executeGraphPathRequestAsync(Session session, String graphPath, Callback callback) {
1202
        return newGraphPathRequest(session, graphPath, callback).executeAsync();
1203
    }
1204
 
1205
    /**
1206
     * Starts a new Request that is configured to perform a search for places near a specified location via the Graph
1207
     * API.
1208
     * <p/>
1209
     * This should only be called from the UI thread.
1210
     *
1211
     * This method is deprecated. Prefer to call Request.newPlacesSearchRequest(...).executeAsync();
1212
     *
1213
     * @param session
1214
     *            the Session to use, or null; if non-null, the session must be in an opened state
1215
     * @param location
1216
     *            the location around which to search; only the latitude and longitude components of the location are
1217
     *            meaningful
1218
     * @param radiusInMeters
1219
     *            the radius around the location to search, specified in meters
1220
     * @param resultsLimit
1221
     *            the maximum number of results to return
1222
     * @param searchText
1223
     *            optional text to search for as part of the name or type of an object
1224
     * @param callback
1225
     *            a callback that will be called when the request is completed to handle success or error conditions
1226
     * @return a RequestAsyncTask that is executing the request
1227
     *
1228
     * @throws FacebookException If neither location nor searchText is specified
1229
     */
1230
    @Deprecated
1231
    public static RequestAsyncTask executePlacesSearchRequestAsync(Session session, Location location,
1232
            int radiusInMeters, int resultsLimit, String searchText, GraphPlaceListCallback callback) {
1233
        return newPlacesSearchRequest(session, location, radiusInMeters, resultsLimit, searchText, callback)
1234
                .executeAsync();
1235
    }
1236
 
1237
    /**
1238
     * Starts a new Request configured to post a status update to a user's feed.
1239
     * <p/>
1240
     * This should only be called from the UI thread.
1241
     *
1242
     * This method is deprecated. Prefer to call Request.newStatusUpdateRequest(...).executeAsync();
1243
     *
1244
     * @param session
1245
     *            the Session to use, or null; if non-null, the session must be in an opened state
1246
     * @param message
1247
     *            the text of the status update
1248
     * @param callback
1249
     *            a callback that will be called when the request is completed to handle success or error conditions
1250
     * @return a RequestAsyncTask that is executing the request
1251
     */
1252
    @Deprecated
1253
    public static RequestAsyncTask executeStatusUpdateRequestAsync(Session session, String message, Callback callback) {
1254
        return newStatusUpdateRequest(session, message, callback).executeAsync();
1255
    }
1256
 
1257
    /**
1258
     * Executes this request and returns the response.
1259
     * <p/>
1260
     * This should only be called if you have transitioned off the UI thread.
1261
     *
1262
     * @return the Response object representing the results of the request
1263
     *
1264
     * @throws FacebookException
1265
     *            If there was an error in the protocol used to communicate with the service
1266
     * @throws IllegalArgumentException
1267
     */
1268
    public final Response executeAndWait() {
1269
        return Request.executeAndWait(this);
1270
    }
1271
 
1272
    /**
1273
     * Executes this request and returns the response.
1274
     * <p/>
1275
     * This should only be called from the UI thread.
1276
     *
1277
     * @return a RequestAsyncTask that is executing the request
1278
     *
1279
     * @throws IllegalArgumentException
1280
     */
1281
    public final RequestAsyncTask executeAsync() {
1282
        return Request.executeBatchAsync(this);
1283
    }
1284
 
1285
    /**
1286
     * Serializes one or more requests but does not execute them. The resulting HttpURLConnection can be executed
1287
     * explicitly by the caller.
1288
     *
1289
     * @param requests
1290
     *            one or more Requests to serialize
1291
     * @return an HttpURLConnection which is ready to execute
1292
     *
1293
     * @throws FacebookException
1294
     *            If any of the requests in the batch are badly constructed or if there are problems
1295
     *            contacting the service
1296
     * @throws IllegalArgumentException if the passed in array is zero-length
1297
     * @throws NullPointerException if the passed in array or any of its contents are null
1298
     */
1299
    public static HttpURLConnection toHttpConnection(Request... requests) {
1300
        return toHttpConnection(Arrays.asList(requests));
1301
    }
1302
 
1303
    /**
1304
     * Serializes one or more requests but does not execute them. The resulting HttpURLConnection can be executed
1305
     * explicitly by the caller.
1306
     *
1307
     * @param requests
1308
     *            one or more Requests to serialize
1309
     * @return an HttpURLConnection which is ready to execute
1310
     *
1311
     * @throws FacebookException
1312
     *            If any of the requests in the batch are badly constructed or if there are problems
1313
     *            contacting the service
1314
     * @throws IllegalArgumentException if the passed in collection is empty
1315
     * @throws NullPointerException if the passed in collection or any of its contents are null
1316
     */
1317
    public static HttpURLConnection toHttpConnection(Collection<Request> requests) {
1318
        Validate.notEmptyAndContainsNoNulls(requests, "requests");
1319
 
1320
        return toHttpConnection(new RequestBatch(requests));
1321
    }
1322
 
1323
 
1324
    /**
1325
     * Serializes one or more requests but does not execute them. The resulting HttpURLConnection can be executed
1326
     * explicitly by the caller.
1327
     *
1328
     * @param requests
1329
     *            a RequestBatch to serialize
1330
     * @return an HttpURLConnection which is ready to execute
1331
     *
1332
     * @throws FacebookException
1333
     *            If any of the requests in the batch are badly constructed or if there are problems
1334
     *            contacting the service
1335
     * @throws IllegalArgumentException
1336
     */
1337
    public static HttpURLConnection toHttpConnection(RequestBatch requests) {
1338
 
1339
        URL url = null;
1340
        try {
1341
            if (requests.size() == 1) {
1342
                // Single request case.
1343
                Request request = requests.get(0);
1344
                // In the non-batch case, the URL we use really is the same one returned by getUrlForSingleRequest.
1345
                url = new URL(request.getUrlForSingleRequest());
1346
            } else {
1347
                // Batch case -- URL is just the graph API base, individual request URLs are serialized
1348
                // as relative_url parameters within each batch entry.
1349
                url = new URL(ServerProtocol.getGraphUrlBase());
1350
            }
1351
        } catch (MalformedURLException e) {
1352
            throw new FacebookException("could not construct URL for request", e);
1353
        }
1354
 
1355
        HttpURLConnection connection;
1356
        try {
1357
            connection = createConnection(url);
1358
 
1359
            serializeToUrlConnection(requests, connection);
1360
        } catch (IOException e) {
1361
            throw new FacebookException("could not construct request body", e);
1362
        } catch (JSONException e) {
1363
            throw new FacebookException("could not construct request body", e);
1364
        }
1365
 
1366
        return connection;
1367
    }
1368
 
1369
    /**
1370
     * Executes a single request on the current thread and returns the response.
1371
     * <p/>
1372
     * This should only be used if you have transitioned off the UI thread.
1373
     *
1374
     * @param request
1375
     *            the Request to execute
1376
     *
1377
     * @return the Response object representing the results of the request
1378
     *
1379
     * @throws FacebookException
1380
     *            If there was an error in the protocol used to communicate with the service
1381
     */
1382
    public static Response executeAndWait(Request request) {
1383
        List<Response> responses = executeBatchAndWait(request);
1384
 
1385
        if (responses == null || responses.size() != 1) {
1386
            throw new FacebookException("invalid state: expected a single response");
1387
        }
1388
 
1389
        return responses.get(0);
1390
    }
1391
 
1392
    /**
1393
     * Executes requests on the current thread as a single batch and returns the responses.
1394
     * <p/>
1395
     * This should only be used if you have transitioned off the UI thread.
1396
     *
1397
     * @param requests
1398
     *            the Requests to execute
1399
     *
1400
     * @return a list of Response objects representing the results of the requests; responses are returned in the same
1401
     *         order as the requests were specified.
1402
     *
1403
     * @throws NullPointerException
1404
     *            In case of a null request
1405
     * @throws FacebookException
1406
     *            If there was an error in the protocol used to communicate with the service
1407
     */
1408
    public static List<Response> executeBatchAndWait(Request... requests) {
1409
        Validate.notNull(requests, "requests");
1410
 
1411
        return executeBatchAndWait(Arrays.asList(requests));
1412
    }
1413
 
1414
    /**
1415
     * Executes requests as a single batch on the current thread and returns the responses.
1416
     * <p/>
1417
     * This should only be used if you have transitioned off the UI thread.
1418
     *
1419
     * @param requests
1420
     *            the Requests to execute
1421
     *
1422
     * @return a list of Response objects representing the results of the requests; responses are returned in the same
1423
     *         order as the requests were specified.
1424
     *
1425
     * @throws FacebookException
1426
     *            If there was an error in the protocol used to communicate with the service
1427
     */
1428
    public static List<Response> executeBatchAndWait(Collection<Request> requests) {
1429
        return executeBatchAndWait(new RequestBatch(requests));
1430
    }
1431
 
1432
    /**
1433
     * Executes requests on the current thread as a single batch and returns the responses.
1434
     * <p/>
1435
     * This should only be used if you have transitioned off the UI thread.
1436
     *
1437
     * @param requests
1438
     *            the batch of Requests to execute
1439
     *
1440
     * @return a list of Response objects representing the results of the requests; responses are returned in the same
1441
     *         order as the requests were specified.
1442
     *
1443
     * @throws FacebookException
1444
     *            If there was an error in the protocol used to communicate with the service
1445
     * @throws IllegalArgumentException if the passed in RequestBatch is empty
1446
     * @throws NullPointerException if the passed in RequestBatch or any of its contents are null
1447
     */
1448
    public static List<Response> executeBatchAndWait(RequestBatch requests) {
1449
        Validate.notEmptyAndContainsNoNulls(requests, "requests");
1450
 
1451
        HttpURLConnection connection = null;
1452
        try {
1453
            connection = toHttpConnection(requests);
1454
        } catch (Exception ex) {
1455
            List<Response> responses = Response.constructErrorResponses(requests.getRequests(), null, new FacebookException(ex));
1456
            runCallbacks(requests, responses);
1457
            return responses;
1458
        }
1459
 
1460
        List<Response> responses = executeConnectionAndWait(connection, requests);
1461
        return responses;
1462
    }
1463
 
1464
    /**
1465
     * Executes requests as a single batch asynchronously. This function will return immediately, and the requests will
1466
     * be processed on a separate thread. In order to process results of a request, or determine whether a request
1467
     * succeeded or failed, a callback must be specified (see the {@link #setCallback(Callback) setCallback} method).
1468
     * <p/>
1469
     * This should only be called from the UI thread.
1470
     *
1471
     * @param requests
1472
     *            the Requests to execute
1473
     * @return a RequestAsyncTask that is executing the request
1474
     *
1475
     * @throws NullPointerException
1476
     *            If a null request is passed in
1477
     */
1478
    public static RequestAsyncTask executeBatchAsync(Request... requests) {
1479
        Validate.notNull(requests, "requests");
1480
 
1481
        return executeBatchAsync(Arrays.asList(requests));
1482
    }
1483
 
1484
    /**
1485
     * Executes requests as a single batch asynchronously. This function will return immediately, and the requests will
1486
     * be processed on a separate thread. In order to process results of a request, or determine whether a request
1487
     * succeeded or failed, a callback must be specified (see the {@link #setCallback(Callback) setCallback} method).
1488
     * <p/>
1489
     * This should only be called from the UI thread.
1490
     *
1491
     * @param requests
1492
     *            the Requests to execute
1493
     * @return a RequestAsyncTask that is executing the request
1494
     *
1495
     * @throws IllegalArgumentException if the passed in collection is empty
1496
     * @throws NullPointerException if the passed in collection or any of its contents are null
1497
     */
1498
    public static RequestAsyncTask executeBatchAsync(Collection<Request> requests) {
1499
        return executeBatchAsync(new RequestBatch(requests));
1500
    }
1501
 
1502
    /**
1503
     * Executes requests as a single batch asynchronously. This function will return immediately, and the requests will
1504
     * be processed on a separate thread. In order to process results of a request, or determine whether a request
1505
     * succeeded or failed, a callback must be specified (see the {@link #setCallback(Callback) setCallback} method).
1506
     * <p/>
1507
     * This should only be called from the UI thread.
1508
     *
1509
     * @param requests
1510
     *            the RequestBatch to execute
1511
     * @return a RequestAsyncTask that is executing the request
1512
     *
1513
     * @throws IllegalArgumentException if the passed in RequestBatch is empty
1514
     * @throws NullPointerException if the passed in RequestBatch or any of its contents are null
1515
     */
1516
    public static RequestAsyncTask executeBatchAsync(RequestBatch requests) {
1517
        Validate.notEmptyAndContainsNoNulls(requests, "requests");
1518
 
1519
        RequestAsyncTask asyncTask = new RequestAsyncTask(requests);
1520
        asyncTask.executeOnSettingsExecutor();
1521
        return asyncTask;
1522
    }
1523
 
1524
    /**
1525
     * Executes requests that have already been serialized into an HttpURLConnection. No validation is done that the
1526
     * contents of the connection actually reflect the serialized requests, so it is the caller's responsibility to
1527
     * ensure that it will correctly generate the desired responses.
1528
     * <p/>
1529
     * This should only be called if you have transitioned off the UI thread.
1530
     *
1531
     * @param connection
1532
     *            the HttpURLConnection that the requests were serialized into
1533
     * @param requests
1534
     *            the requests represented by the HttpURLConnection
1535
     * @return a list of Responses corresponding to the requests
1536
     *
1537
     * @throws FacebookException
1538
     *            If there was an error in the protocol used to communicate with the service
1539
     */
1540
    public static List<Response> executeConnectionAndWait(HttpURLConnection connection, Collection<Request> requests) {
1541
        return executeConnectionAndWait(connection, new RequestBatch(requests));
1542
    }
1543
 
1544
    /**
1545
     * Executes requests that have already been serialized into an HttpURLConnection. No validation is done that the
1546
     * contents of the connection actually reflect the serialized requests, so it is the caller's responsibility to
1547
     * ensure that it will correctly generate the desired responses.
1548
     * <p/>
1549
     * This should only be called if you have transitioned off the UI thread.
1550
     *
1551
     * @param connection
1552
     *            the HttpURLConnection that the requests were serialized into
1553
     * @param requests
1554
     *            the RequestBatch represented by the HttpURLConnection
1555
     * @return a list of Responses corresponding to the requests
1556
     *
1557
     * @throws FacebookException
1558
     *            If there was an error in the protocol used to communicate with the service
1559
     */
1560
    public static List<Response> executeConnectionAndWait(HttpURLConnection connection, RequestBatch requests) {
1561
        List<Response> responses = Response.fromHttpConnection(connection, requests);
1562
 
1563
        Utility.disconnectQuietly(connection);
1564
 
1565
        int numRequests = requests.size();
1566
        if (numRequests != responses.size()) {
1567
            throw new FacebookException(String.format("Received %d responses while expecting %d", responses.size(),
1568
                    numRequests));
1569
        }
1570
 
1571
        runCallbacks(requests, responses);
1572
 
1573
        // See if any of these sessions needs its token to be extended. We do this after issuing the request so as to
1574
        // reduce network contention.
1575
        HashSet<Session> sessions = new HashSet<Session>();
1576
        for (Request request : requests) {
1577
            if (request.session != null) {
1578
                sessions.add(request.session);
1579
            }
1580
        }
1581
        for (Session session : sessions) {
1582
            session.extendAccessTokenIfNeeded();
1583
        }
1584
 
1585
        return responses;
1586
    }
1587
 
1588
    /**
1589
     * Asynchronously executes requests that have already been serialized into an HttpURLConnection. No validation is
1590
     * done that the contents of the connection actually reflect the serialized requests, so it is the caller's
1591
     * responsibility to ensure that it will correctly generate the desired responses. This function will return
1592
     * immediately, and the requests will be processed on a separate thread. In order to process results of a request,
1593
     * or determine whether a request succeeded or failed, a callback must be specified (see the
1594
     * {@link #setCallback(Callback) setCallback} method).
1595
     * <p/>
1596
     * This should only be called from the UI thread.
1597
     *
1598
     * @param connection
1599
     *            the HttpURLConnection that the requests were serialized into
1600
     * @param requests
1601
     *            the requests represented by the HttpURLConnection
1602
     * @return a RequestAsyncTask that is executing the request
1603
     */
1604
    public static RequestAsyncTask executeConnectionAsync(HttpURLConnection connection, RequestBatch requests) {
1605
        return executeConnectionAsync(null, connection, requests);
1606
    }
1607
 
1608
    /**
1609
     * Asynchronously executes requests that have already been serialized into an HttpURLConnection. No validation is
1610
     * done that the contents of the connection actually reflect the serialized requests, so it is the caller's
1611
     * responsibility to ensure that it will correctly generate the desired responses. This function will return
1612
     * immediately, and the requests will be processed on a separate thread. In order to process results of a request,
1613
     * or determine whether a request succeeded or failed, a callback must be specified (see the
1614
     * {@link #setCallback(Callback) setCallback} method)
1615
     * <p/>
1616
     * This should only be called from the UI thread.
1617
     *
1618
     * @param callbackHandler
1619
     *            a Handler that will be used to post calls to the callback for each request; if null, a Handler will be
1620
     *            instantiated on the calling thread
1621
     * @param connection
1622
     *            the HttpURLConnection that the requests were serialized into
1623
     * @param requests
1624
     *            the requests represented by the HttpURLConnection
1625
     * @return a RequestAsyncTask that is executing the request
1626
     */
1627
    public static RequestAsyncTask executeConnectionAsync(Handler callbackHandler, HttpURLConnection connection,
1628
            RequestBatch requests) {
1629
        Validate.notNull(connection, "connection");
1630
 
1631
        RequestAsyncTask asyncTask = new RequestAsyncTask(connection, requests);
1632
        requests.setCallbackHandler(callbackHandler);
1633
        asyncTask.executeOnSettingsExecutor();
1634
        return asyncTask;
1635
    }
1636
 
1637
    /**
1638
     * Returns a string representation of this Request, useful for debugging.
1639
     *
1640
     * @return the debugging information
1641
     */
1642
    @Override
1643
    public String toString() {
1644
        return new StringBuilder().append("{Request: ").append(" session: ").append(session).append(", graphPath: ")
1645
                .append(graphPath).append(", graphObject: ").append(graphObject)
1646
                .append(", httpMethod: ").append(httpMethod).append(", parameters: ")
1647
                .append(parameters).append("}").toString();
1648
    }
1649
 
1650
    static void runCallbacks(final RequestBatch requests, List<Response> responses) {
1651
        int numRequests = requests.size();
1652
 
1653
        // Compile the list of callbacks to call and then run them either on this thread or via the Handler we received
1654
        final ArrayList<Pair<Callback, Response>> callbacks = new ArrayList<Pair<Callback, Response>>();
1655
        for (int i = 0; i < numRequests; ++i) {
1656
            Request request = requests.get(i);
1657
            if (request.callback != null) {
1658
                callbacks.add(new Pair<Callback, Response>(request.callback, responses.get(i)));
1659
            }
1660
        }
1661
 
1662
        if (callbacks.size() > 0) {
1663
            Runnable runnable = new Runnable() {
1664
                public void run() {
1665
                    for (Pair<Callback, Response> pair : callbacks) {
1666
                        pair.first.onCompleted(pair.second);
1667
                    }
1668
 
1669
                    List<RequestBatch.Callback> batchCallbacks = requests.getCallbacks();
1670
                    for (RequestBatch.Callback batchCallback : batchCallbacks) {
1671
                        batchCallback.onBatchCompleted(requests);
1672
                    }
1673
                }
1674
            };
1675
 
1676
            Handler callbackHandler = requests.getCallbackHandler();
1677
            if (callbackHandler == null) {
1678
                // Run on this thread.
1679
                runnable.run();
1680
            } else {
1681
                // Post to the handler.
1682
                callbackHandler.post(runnable);
1683
            }
1684
        }
1685
    }
1686
 
1687
    static HttpURLConnection createConnection(URL url) throws IOException {
1688
        HttpURLConnection connection;
1689
        connection = (HttpURLConnection) url.openConnection();
1690
 
1691
        connection.setRequestProperty(USER_AGENT_HEADER, getUserAgent());
1692
        connection.setRequestProperty(CONTENT_TYPE_HEADER, getMimeContentType());
1693
        connection.setRequestProperty(ACCEPT_LANGUAGE_HEADER, Locale.getDefault().toString());
1694
 
1695
        connection.setChunkedStreamingMode(0);
1696
        return connection;
1697
    }
1698
 
1699
 
1700
    private void addCommonParameters() {
1701
        if (this.session != null) {
1702
            if (!this.session.isOpened()) {
1703
                throw new FacebookException("Session provided to a Request in un-opened state.");
1704
            } else if (!this.parameters.containsKey(ACCESS_TOKEN_PARAM)) {
1705
                String accessToken = this.session.getAccessToken();
1706
                Logger.registerAccessToken(accessToken);
1707
                this.parameters.putString(ACCESS_TOKEN_PARAM, accessToken);
1708
            }
1709
        } else if (!skipClientToken && !this.parameters.containsKey(ACCESS_TOKEN_PARAM)) {
1710
            String appID = Settings.getApplicationId();
1711
            String clientToken = Settings.getClientToken();
1712
            if (!Utility.isNullOrEmpty(appID) && !Utility.isNullOrEmpty(clientToken)) {
1713
                String accessToken = appID + "|" + clientToken;
1714
                this.parameters.putString(ACCESS_TOKEN_PARAM, accessToken);
1715
            } else {
1716
                Log.d(TAG,
1717
                        "Warning: Sessionless Request needs token but missing either application ID or client token.");
1718
            }
1719
        }
1720
        this.parameters.putString(SDK_PARAM, SDK_ANDROID);
1721
        this.parameters.putString(FORMAT_PARAM, FORMAT_JSON);
1722
    }
1723
 
1724
    private String appendParametersToBaseUrl(String baseUrl) {
1725
        Uri.Builder uriBuilder = new Uri.Builder().encodedPath(baseUrl);
1726
 
1727
        Set<String> keys = this.parameters.keySet();
1728
        for (String key : keys) {
1729
            Object value = this.parameters.get(key);
1730
 
1731
            if (value == null) {
1732
                value = "";
1733
            }
1734
 
1735
            if (isSupportedParameterType(value)) {
1736
                value = parameterToString(value);
1737
            } else {
1738
                if (httpMethod == HttpMethod.GET) {
1739
                    throw new IllegalArgumentException(String.format("Unsupported parameter type for GET request: %s",
1740
                                    value.getClass().getSimpleName()));
1741
                }
1742
                continue;
1743
            }
1744
 
1745
            uriBuilder.appendQueryParameter(key, value.toString());
1746
        }
1747
 
1748
        return uriBuilder.toString();
1749
    }
1750
 
1751
    final String getUrlForBatchedRequest() {
1752
        if (overriddenURL != null) {
1753
            throw new FacebookException("Can't override URL for a batch request");
1754
        }
1755
 
1756
        String baseUrl = getGraphPathWithVersion();
1757
        addCommonParameters();
1758
        return appendParametersToBaseUrl(baseUrl);
1759
    }
1760
 
1761
    final String getUrlForSingleRequest() {
1762
        if (overriddenURL != null) {
1763
            return overriddenURL.toString();
1764
        }
1765
 
1766
        String graphBaseUrlBase;
1767
        if (this.getHttpMethod() == HttpMethod.POST && graphPath != null && graphPath.endsWith(VIDEOS_SUFFIX)) {
1768
            graphBaseUrlBase = ServerProtocol.getGraphVideoUrlBase();
1769
        } else {
1770
            graphBaseUrlBase = ServerProtocol.getGraphUrlBase();
1771
        }
1772
        String baseUrl = String.format("%s/%s", graphBaseUrlBase, getGraphPathWithVersion());
1773
 
1774
        addCommonParameters();
1775
        return appendParametersToBaseUrl(baseUrl);
1776
    }
1777
 
1778
    private String getGraphPathWithVersion() {
1779
        Matcher matcher = versionPattern.matcher(this.graphPath);
1780
        if (matcher.matches()) {
1781
            return this.graphPath;
1782
        }
1783
        return String.format("%s/%s", this.version, this.graphPath);
1784
    }
1785
 
1786
    private static class Attachment {
1787
        private final Request request;
1788
        private final Object value;
1789
 
1790
        public Attachment(Request request, Object value) {
1791
            this.request = request;
1792
            this.value = value;
1793
        }
1794
 
1795
        public Request getRequest() {
1796
            return request;
1797
        }
1798
 
1799
        public Object getValue() {
1800
            return value;
1801
        }
1802
    }
1803
 
1804
    private void serializeToBatch(JSONArray batch, Map<String, Attachment> attachments) throws JSONException, IOException {
1805
        JSONObject batchEntry = new JSONObject();
1806
 
1807
        if (this.batchEntryName != null) {
1808
            batchEntry.put(BATCH_ENTRY_NAME_PARAM, this.batchEntryName);
1809
            batchEntry.put(BATCH_ENTRY_OMIT_RESPONSE_ON_SUCCESS_PARAM, this.batchEntryOmitResultOnSuccess);
1810
        }
1811
        if (this.batchEntryDependsOn != null) {
1812
            batchEntry.put(BATCH_ENTRY_DEPENDS_ON_PARAM, this.batchEntryDependsOn);
1813
        }
1814
 
1815
        String relativeURL = getUrlForBatchedRequest();
1816
        batchEntry.put(BATCH_RELATIVE_URL_PARAM, relativeURL);
1817
        batchEntry.put(BATCH_METHOD_PARAM, httpMethod);
1818
        if (this.session != null) {
1819
            String accessToken = this.session.getAccessToken();
1820
            Logger.registerAccessToken(accessToken);
1821
        }
1822
 
1823
        // Find all of our attachments. Remember their names and put them in the attachment map.
1824
        ArrayList<String> attachmentNames = new ArrayList<String>();
1825
        Set<String> keys = this.parameters.keySet();
1826
        for (String key : keys) {
1827
            Object value = this.parameters.get(key);
1828
            if (isSupportedAttachmentType(value)) {
1829
                // Make the name unique across this entire batch.
1830
                String name = String.format("%s%d", ATTACHMENT_FILENAME_PREFIX, attachments.size());
1831
                attachmentNames.add(name);
1832
                attachments.put(name, new Attachment(this, value));
1833
            }
1834
        }
1835
 
1836
        if (!attachmentNames.isEmpty()) {
1837
            String attachmentNamesString = TextUtils.join(",", attachmentNames);
1838
            batchEntry.put(ATTACHED_FILES_PARAM, attachmentNamesString);
1839
        }
1840
 
1841
        if (this.graphObject != null) {
1842
            // Serialize the graph object into the "body" parameter.
1843
            final ArrayList<String> keysAndValues = new ArrayList<String>();
1844
            processGraphObject(this.graphObject, relativeURL, new KeyValueSerializer() {
1845
                @Override
1846
                public void writeString(String key, String value) throws IOException {
1847
                    keysAndValues.add(String.format("%s=%s", key, URLEncoder.encode(value, "UTF-8")));
1848
                }
1849
            });
1850
            String bodyValue = TextUtils.join("&", keysAndValues);
1851
            batchEntry.put(BATCH_BODY_PARAM, bodyValue);
1852
        }
1853
 
1854
        batch.put(batchEntry);
1855
    }
1856
 
1857
    private static boolean hasOnProgressCallbacks(RequestBatch requests) {
1858
        for (RequestBatch.Callback callback : requests.getCallbacks()) {
1859
            if (callback instanceof RequestBatch.OnProgressCallback) {
1860
                return true;
1861
            }
1862
        }
1863
 
1864
        for (Request request : requests) {
1865
            if (request.getCallback() instanceof OnProgressCallback) {
1866
                return true;
1867
            }
1868
        }
1869
 
1870
        return false;
1871
    }
1872
 
1873
    final static void serializeToUrlConnection(RequestBatch requests, HttpURLConnection connection)
1874
    throws IOException, JSONException {
1875
        Logger logger = new Logger(LoggingBehavior.REQUESTS, "Request");
1876
 
1877
        int numRequests = requests.size();
1878
 
1879
        HttpMethod connectionHttpMethod = (numRequests == 1) ? requests.get(0).httpMethod : HttpMethod.POST;
1880
        connection.setRequestMethod(connectionHttpMethod.name());
1881
 
1882
        URL url = connection.getURL();
1883
        logger.append("Request:\n");
1884
        logger.appendKeyValue("Id", requests.getId());
1885
        logger.appendKeyValue("URL", url);
1886
        logger.appendKeyValue("Method", connection.getRequestMethod());
1887
        logger.appendKeyValue("User-Agent", connection.getRequestProperty("User-Agent"));
1888
        logger.appendKeyValue("Content-Type", connection.getRequestProperty("Content-Type"));
1889
 
1890
        connection.setConnectTimeout(requests.getTimeout());
1891
        connection.setReadTimeout(requests.getTimeout());
1892
 
1893
        // If we have a single non-POST request, don't try to serialize anything or HttpURLConnection will
1894
        // turn it into a POST.
1895
        boolean isPost = (connectionHttpMethod == HttpMethod.POST);
1896
        if (!isPost) {
1897
            logger.log();
1898
            return;
1899
        }
1900
 
1901
        connection.setDoOutput(true);
1902
 
1903
        OutputStream outputStream = null;
1904
        try {
1905
            if (hasOnProgressCallbacks(requests)) {
1906
                ProgressNoopOutputStream countingStream = null;
1907
                countingStream = new ProgressNoopOutputStream(requests.getCallbackHandler());
1908
                processRequest(requests, null, numRequests, url, countingStream);
1909
 
1910
                int max = countingStream.getMaxProgress();
1911
                Map<Request, RequestProgress> progressMap = countingStream.getProgressMap();
1912
 
1913
                BufferedOutputStream buffered = new BufferedOutputStream(connection.getOutputStream());
1914
                outputStream = new ProgressOutputStream(buffered, requests, progressMap, max);
1915
            }
1916
            else {
1917
                outputStream = new BufferedOutputStream(connection.getOutputStream());
1918
            }
1919
 
1920
            processRequest(requests, logger, numRequests, url, outputStream);
1921
        }
1922
        finally {
1923
            if (outputStream != null) {
1924
                outputStream.close();
1925
            }
1926
        }
1927
 
1928
        logger.log();
1929
    }
1930
 
1931
    private static void processRequest(RequestBatch requests, Logger logger, int numRequests, URL url, OutputStream outputStream)
1932
            throws IOException, JSONException
1933
    {
1934
        Serializer serializer = new Serializer(outputStream, logger);
1935
 
1936
        if (numRequests == 1) {
1937
            Request request = requests.get(0);
1938
 
1939
            Map<String, Attachment> attachments = new HashMap<String, Attachment>();
1940
            for(String key : request.parameters.keySet()) {
1941
                Object value = request.parameters.get(key);
1942
                if (isSupportedAttachmentType(value)) {
1943
                    attachments.put(key, new Attachment(request, value));
1944
                }
1945
            }
1946
 
1947
            if (logger != null) {
1948
                logger.append("  Parameters:\n");
1949
            }
1950
            serializeParameters(request.parameters, serializer, request);
1951
 
1952
            if (logger != null) {
1953
                logger.append("  Attachments:\n");
1954
            }
1955
            serializeAttachments(attachments, serializer);
1956
 
1957
            if (request.graphObject != null) {
1958
                processGraphObject(request.graphObject, url.getPath(), serializer);
1959
            }
1960
        } else {
1961
            String batchAppID = getBatchAppId(requests);
1962
            if (Utility.isNullOrEmpty(batchAppID)) {
1963
                throw new FacebookException("At least one request in a batch must have an open Session, or a "
1964
                        + "default app ID must be specified.");
1965
            }
1966
 
1967
            serializer.writeString(BATCH_APP_ID_PARAM, batchAppID);
1968
 
1969
            // We write out all the requests as JSON, remembering which file attachments they have, then
1970
            // write out the attachments.
1971
            Map<String, Attachment> attachments = new HashMap<String, Attachment>();
1972
            serializeRequestsAsJSON(serializer, requests, attachments);
1973
 
1974
            if (logger != null) {
1975
                logger.append("  Attachments:\n");
1976
            }
1977
            serializeAttachments(attachments, serializer);
1978
        }
1979
    }
1980
 
1981
    private static boolean isMeRequest(String path) {
1982
        Matcher matcher = versionPattern.matcher(path);
1983
        if (matcher.matches()) {
1984
            // Group 1 contains the path aside from version
1985
            path = matcher.group(1);
1986
        }
1987
        if (path.startsWith("me/") || path.startsWith("/me/")) {
1988
            return true;
1989
        }
1990
        return false;
1991
    }
1992
 
1993
    private static void processGraphObject(GraphObject graphObject, String path, KeyValueSerializer serializer)
1994
            throws IOException {
1995
        // In general, graph objects are passed by reference (ID/URL). But if this is an OG Action,
1996
        // we need to pass the entire values of the contents of the 'image' property, as they
1997
        // contain important metadata beyond just a URL. We don't have a 100% foolproof way of knowing
1998
        // if we are posting an OG Action, given that batched requests can have parameter substitution,
1999
        // but passing the OG Action type as a substituted parameter is unlikely.
2000
        // It looks like an OG Action if it's posted to me/namespace:action[?other=stuff].
2001
        boolean isOGAction = false;
2002
        if (isMeRequest(path)) {
2003
            int colonLocation = path.indexOf(":");
2004
            int questionMarkLocation = path.indexOf("?");
2005
            isOGAction = colonLocation > 3 && (questionMarkLocation == -1 || colonLocation < questionMarkLocation);
2006
        }
2007
 
2008
        Set<Entry<String, Object>> entries = graphObject.asMap().entrySet();
2009
        for (Entry<String, Object> entry : entries) {
2010
            boolean passByValue = isOGAction && entry.getKey().equalsIgnoreCase("image");
2011
            processGraphObjectProperty(entry.getKey(), entry.getValue(), serializer, passByValue);
2012
        }
2013
    }
2014
 
2015
    private static void processGraphObjectProperty(String key, Object value, KeyValueSerializer serializer,
2016
            boolean passByValue) throws IOException {
2017
        Class<?> valueClass = value.getClass();
2018
        if (GraphObject.class.isAssignableFrom(valueClass)) {
2019
            value = ((GraphObject) value).getInnerJSONObject();
2020
            valueClass = value.getClass();
2021
        } else if (GraphObjectList.class.isAssignableFrom(valueClass)) {
2022
            value = ((GraphObjectList<?>) value).getInnerJSONArray();
2023
            valueClass = value.getClass();
2024
        }
2025
 
2026
        if (JSONObject.class.isAssignableFrom(valueClass)) {
2027
            JSONObject jsonObject = (JSONObject) value;
2028
            if (passByValue) {
2029
                // We need to pass all properties of this object in key[propertyName] format.
2030
                @SuppressWarnings("unchecked")
2031
                Iterator<String> keys = jsonObject.keys();
2032
                while (keys.hasNext()) {
2033
                    String propertyName = keys.next();
2034
                    String subKey = String.format("%s[%s]", key, propertyName);
2035
                    processGraphObjectProperty(subKey, jsonObject.opt(propertyName), serializer, passByValue);
2036
                }
2037
            } else {
2038
                // Normal case is passing objects by reference, so just pass the ID or URL, if any, as the value
2039
                // for "key"
2040
                if (jsonObject.has("id")) {
2041
                    processGraphObjectProperty(key, jsonObject.optString("id"), serializer, passByValue);
2042
                } else if (jsonObject.has("url")) {
2043
                    processGraphObjectProperty(key, jsonObject.optString("url"), serializer, passByValue);
2044
                } else if (jsonObject.has(NativeProtocol.OPEN_GRAPH_CREATE_OBJECT_KEY)) {
2045
                    processGraphObjectProperty(key, jsonObject.toString(), serializer, passByValue);
2046
                }
2047
            }
2048
        } else if (JSONArray.class.isAssignableFrom(valueClass)) {
2049
            JSONArray jsonArray = (JSONArray) value;
2050
            int length = jsonArray.length();
2051
            for (int i = 0; i < length; ++i) {
2052
                String subKey = String.format("%s[%d]", key, i);
2053
                processGraphObjectProperty(subKey, jsonArray.opt(i), serializer, passByValue);
2054
            }
2055
        } else if (String.class.isAssignableFrom(valueClass) ||
2056
                Number.class.isAssignableFrom(valueClass) ||
2057
                Boolean.class.isAssignableFrom(valueClass)) {
2058
            serializer.writeString(key, value.toString());
2059
        } else if (Date.class.isAssignableFrom(valueClass)) {
2060
            Date date = (Date) value;
2061
            // The "Events Timezone" platform migration affects what date/time formats Facebook accepts and returns.
2062
            // Apps created after 8/1/12 (or apps that have explicitly enabled the migration) should send/receive
2063
            // dates in ISO-8601 format. Pre-migration apps can send as Unix timestamps. Since the future is ISO-8601,
2064
            // that is what we support here. Apps that need pre-migration behavior can explicitly send these as
2065
            // integer timestamps rather than Dates.
2066
            final SimpleDateFormat iso8601DateFormat = new SimpleDateFormat(ISO_8601_FORMAT_STRING, Locale.US);
2067
            serializer.writeString(key, iso8601DateFormat.format(date));
2068
        }
2069
    }
2070
 
2071
    private static void serializeParameters(Bundle bundle, Serializer serializer, Request request) throws IOException {
2072
        Set<String> keys = bundle.keySet();
2073
 
2074
        for (String key : keys) {
2075
            Object value = bundle.get(key);
2076
            if (isSupportedParameterType(value)) {
2077
                serializer.writeObject(key, value, request);
2078
            }
2079
        }
2080
    }
2081
 
2082
    private static void serializeAttachments(Map<String, Attachment> attachments, Serializer serializer) throws IOException {
2083
        Set<String> keys = attachments.keySet();
2084
 
2085
        for (String key : keys) {
2086
            Attachment attachment = attachments.get(key);
2087
            if (isSupportedAttachmentType(attachment.getValue())) {
2088
                serializer.writeObject(key, attachment.getValue(), attachment.getRequest());
2089
            }
2090
        }
2091
    }
2092
 
2093
    private static void serializeRequestsAsJSON(Serializer serializer, Collection<Request> requests, Map<String, Attachment> attachments)
2094
            throws JSONException, IOException {
2095
        JSONArray batch = new JSONArray();
2096
        for (Request request : requests) {
2097
            request.serializeToBatch(batch, attachments);
2098
        }
2099
 
2100
        serializer.writeRequestsAsJson(BATCH_PARAM, batch, requests);
2101
    }
2102
 
2103
    private static String getMimeContentType() {
2104
        return String.format("multipart/form-data; boundary=%s", MIME_BOUNDARY);
2105
    }
2106
 
2107
    private static volatile String userAgent;
2108
 
2109
    private static String getUserAgent() {
2110
        if (userAgent == null) {
2111
            userAgent = String.format("%s.%s", USER_AGENT_BASE, FacebookSdkVersion.BUILD);
2112
        }
2113
 
2114
        return userAgent;
2115
    }
2116
 
2117
    private static String getBatchAppId(RequestBatch batch) {
2118
        if (!Utility.isNullOrEmpty(batch.getBatchApplicationId())) {
2119
            return batch.getBatchApplicationId();
2120
        }
2121
 
2122
        for (Request request : batch) {
2123
            Session session = request.session;
2124
            if (session != null) {
2125
                return session.getApplicationId();
2126
            }
2127
        }
2128
        return Request.defaultBatchApplicationId;
2129
    }
2130
 
2131
    private static <T extends GraphObject> List<T> typedListFromResponse(Response response, Class<T> clazz) {
2132
        GraphMultiResult multiResult = response.getGraphObjectAs(GraphMultiResult.class);
2133
        if (multiResult == null) {
2134
            return null;
2135
        }
2136
 
2137
        GraphObjectList<GraphObject> data = multiResult.getData();
2138
        if (data == null) {
2139
            return null;
2140
        }
2141
 
2142
        return data.castToListOf(clazz);
2143
    }
2144
 
2145
    private static boolean isSupportedAttachmentType(Object value) {
2146
        return value instanceof Bitmap || value instanceof byte[] || value instanceof ParcelFileDescriptor ||
2147
                value instanceof ParcelFileDescriptorWithMimeType;
2148
    }
2149
 
2150
    private static boolean isSupportedParameterType(Object value) {
2151
        return value instanceof String || value instanceof Boolean || value instanceof Number ||
2152
                value instanceof Date;
2153
    }
2154
 
2155
    private static String parameterToString(Object value) {
2156
        if (value instanceof String) {
2157
            return (String) value;
2158
        } else if (value instanceof Boolean || value instanceof Number) {
2159
            return value.toString();
2160
        } else if (value instanceof Date) {
2161
            final SimpleDateFormat iso8601DateFormat = new SimpleDateFormat(ISO_8601_FORMAT_STRING, Locale.US);
2162
            return iso8601DateFormat.format(value);
2163
        }
2164
        throw new IllegalArgumentException("Unsupported parameter type.");
2165
    }
2166
 
2167
    private interface KeyValueSerializer {
2168
        void writeString(String key, String value) throws IOException;
2169
    }
2170
 
2171
    private static class Serializer implements KeyValueSerializer {
2172
        private final OutputStream outputStream;
2173
        private final Logger logger;
2174
        private boolean firstWrite = true;
2175
 
2176
        public Serializer(OutputStream outputStream, Logger logger) {
2177
            this.outputStream = outputStream;
2178
            this.logger = logger;
2179
        }
2180
 
2181
        public void writeObject(String key, Object value, Request request) throws IOException {
2182
            if (outputStream instanceof RequestOutputStream) {
2183
                ((RequestOutputStream) outputStream).setCurrentRequest(request);
2184
            }
2185
 
2186
            if (isSupportedParameterType(value)) {
2187
                writeString(key, parameterToString(value));
2188
            } else if (value instanceof Bitmap) {
2189
                writeBitmap(key, (Bitmap) value);
2190
            } else if (value instanceof byte[]) {
2191
                writeBytes(key, (byte[]) value);
2192
            } else if (value instanceof ParcelFileDescriptor) {
2193
                writeFile(key, (ParcelFileDescriptor) value, null);
2194
            } else if (value instanceof ParcelFileDescriptorWithMimeType) {
2195
                writeFile(key, (ParcelFileDescriptorWithMimeType) value);
2196
            } else {
2197
                throw new IllegalArgumentException("value is not a supported type: String, Bitmap, byte[]");
2198
            }
2199
        }
2200
 
2201
        public void writeRequestsAsJson(String key, JSONArray requestJsonArray, Collection<Request> requests)
2202
                throws IOException, JSONException {
2203
            if (! (outputStream instanceof RequestOutputStream)) {
2204
                writeString(key, requestJsonArray.toString());
2205
                return;
2206
            }
2207
 
2208
            RequestOutputStream requestOutputStream = (RequestOutputStream) outputStream;
2209
            writeContentDisposition(key, null, null);
2210
            write("[");
2211
            int i = 0;
2212
            for (Request request : requests) {
2213
                JSONObject requestJson = requestJsonArray.getJSONObject(i);
2214
                requestOutputStream.setCurrentRequest(request);
2215
                if (i > 0) {
2216
                    write(",%s", requestJson.toString());
2217
                } else {
2218
                    write("%s", requestJson.toString());
2219
                }
2220
                i++;
2221
            }
2222
            write("]");
2223
            if (logger != null) {
2224
                logger.appendKeyValue("    " + key, requestJsonArray.toString());
2225
            }
2226
        }
2227
 
2228
        public void writeString(String key, String value) throws IOException {
2229
            writeContentDisposition(key, null, null);
2230
            writeLine("%s", value);
2231
            writeRecordBoundary();
2232
            if (logger != null) {
2233
                logger.appendKeyValue("    " + key, value);
2234
            }
2235
        }
2236
 
2237
        public void writeBitmap(String key, Bitmap bitmap) throws IOException {
2238
            writeContentDisposition(key, key, "image/png");
2239
            // Note: quality parameter is ignored for PNG
2240
            bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
2241
            writeLine("");
2242
            writeRecordBoundary();
2243
            if (logger != null) {
2244
                logger.appendKeyValue("    " + key, "<Image>");
2245
            }
2246
        }
2247
 
2248
        public void writeBytes(String key, byte[] bytes) throws IOException {
2249
            writeContentDisposition(key, key, "content/unknown");
2250
            this.outputStream.write(bytes);
2251
            writeLine("");
2252
            writeRecordBoundary();
2253
            if (logger != null) {
2254
                logger.appendKeyValue("    " + key, String.format("<Data: %d>", bytes.length));
2255
            }
2256
        }
2257
 
2258
        public void writeFile(String key, ParcelFileDescriptorWithMimeType descriptorWithMimeType) throws IOException {
2259
            writeFile(key, descriptorWithMimeType.getFileDescriptor(), descriptorWithMimeType.getMimeType());
2260
        }
2261
 
2262
        public void writeFile(String key, ParcelFileDescriptor descriptor, String mimeType) throws IOException {
2263
            if (mimeType == null) {
2264
                mimeType = "content/unknown";
2265
            }
2266
            writeContentDisposition(key, key, mimeType);
2267
 
2268
            int totalBytes = 0;
2269
 
2270
            if (outputStream instanceof ProgressNoopOutputStream) {
2271
                // If we are only counting bytes then skip reading the file
2272
                ((ProgressNoopOutputStream) outputStream).addProgress(descriptor.getStatSize());
2273
            }
2274
            else {
2275
                ParcelFileDescriptor.AutoCloseInputStream inputStream = null;
2276
                BufferedInputStream bufferedInputStream = null;
2277
                try {
2278
                    inputStream = new ParcelFileDescriptor.AutoCloseInputStream(descriptor);
2279
                    bufferedInputStream = new BufferedInputStream(inputStream);
2280
 
2281
                    byte[] buffer = new byte[8192];
2282
                    int bytesRead;
2283
                    while ((bytesRead = bufferedInputStream.read(buffer)) != -1) {
2284
                        this.outputStream.write(buffer, 0, bytesRead);
2285
                        totalBytes += bytesRead;
2286
                    }
2287
                } finally {
2288
                    if (bufferedInputStream != null) {
2289
                        bufferedInputStream.close();
2290
                    }
2291
                    if (inputStream != null) {
2292
                        inputStream.close();
2293
                    }
2294
                }
2295
            }
2296
            writeLine("");
2297
            writeRecordBoundary();
2298
            if (logger != null) {
2299
                logger.appendKeyValue("    " + key, String.format("<Data: %d>", totalBytes));
2300
            }
2301
        }
2302
 
2303
        public void writeRecordBoundary() throws IOException {
2304
            writeLine("--%s", MIME_BOUNDARY);
2305
        }
2306
 
2307
        public void writeContentDisposition(String name, String filename, String contentType) throws IOException {
2308
            write("Content-Disposition: form-data; name=\"%s\"", name);
2309
            if (filename != null) {
2310
                write("; filename=\"%s\"", filename);
2311
            }
2312
            writeLine(""); // newline after Content-Disposition
2313
            if (contentType != null) {
2314
                writeLine("%s: %s", CONTENT_TYPE_HEADER, contentType);
2315
            }
2316
            writeLine(""); // blank line before content
2317
        }
2318
 
2319
        public void write(String format, Object... args) throws IOException {
2320
            if (firstWrite) {
2321
                // Prepend all of our output with a boundary string.
2322
                this.outputStream.write("--".getBytes());
2323
                this.outputStream.write(MIME_BOUNDARY.getBytes());
2324
                this.outputStream.write("\r\n".getBytes());
2325
                firstWrite = false;
2326
            }
2327
            this.outputStream.write(String.format(format, args).getBytes());
2328
        }
2329
 
2330
        public void writeLine(String format, Object... args) throws IOException {
2331
            write(format, args);
2332
            write("\r\n");
2333
        }
2334
 
2335
    }
2336
 
2337
    /**
2338
     * Specifies the interface that consumers of the Request class can implement in order to be notified when a
2339
     * particular request completes, either successfully or with an error.
2340
     */
2341
    public interface Callback {
2342
        /**
2343
         * The method that will be called when a request completes.
2344
         *
2345
         * @param response
2346
         *            the Response of this request, which may include error information if the request was unsuccessful
2347
         */
2348
        void onCompleted(Response response);
2349
    }
2350
 
2351
    /**
2352
     * Specifies the interface that consumers of the Request class can implement in order to be notified when a
2353
     * progress is made on a particular request. The frequency of the callbacks can be controlled using
2354
     * {@link com.facebook.Settings#setOnProgressThreshold(long)}
2355
     */
2356
    public interface OnProgressCallback extends Callback {
2357
        /**
2358
         * The method that will be called when progress is made.
2359
         *
2360
         * @param current
2361
         *            the current value of the progress of the request.
2362
         * @param max
2363
         *            the maximum value (target) value that the progress will have.
2364
         */
2365
        void onProgress(long current, long max);
2366
    }
2367
 
2368
    /**
2369
     * Specifies the interface that consumers of
2370
     * {@link Request#executeMeRequestAsync(Session, com.facebook.Request.GraphUserCallback)}
2371
     * can use to be notified when the request completes, either successfully or with an error.
2372
     */
2373
    public interface GraphUserCallback {
2374
        /**
2375
         * The method that will be called when the request completes.
2376
         *
2377
         * @param user     the GraphObject representing the returned user, or null
2378
         * @param response the Response of this request, which may include error information if the request was unsuccessful
2379
         */
2380
        void onCompleted(GraphUser user, Response response);
2381
    }
2382
 
2383
    /**
2384
     * Specifies the interface that consumers of
2385
     * {@link Request#executeMyFriendsRequestAsync(Session, com.facebook.Request.GraphUserListCallback)}
2386
     * can use to be notified when the request completes, either successfully or with an error.
2387
     */
2388
    public interface GraphUserListCallback {
2389
        /**
2390
         * The method that will be called when the request completes.
2391
         *
2392
         * @param users    the list of GraphObjects representing the returned friends, or null
2393
         * @param response the Response of this request, which may include error information if the request was unsuccessful
2394
         */
2395
        void onCompleted(List<GraphUser> users, Response response);
2396
    }
2397
 
2398
    /**
2399
     * Specifies the interface that consumers of
2400
     * {@link Request#executePlacesSearchRequestAsync(Session, android.location.Location, int, int, String, com.facebook.Request.GraphPlaceListCallback)}
2401
     * can use to be notified when the request completes, either successfully or with an error.
2402
     */
2403
    public interface GraphPlaceListCallback {
2404
        /**
2405
         * The method that will be called when the request completes.
2406
         *
2407
         * @param places   the list of GraphObjects representing the returned places, or null
2408
         * @param response the Response of this request, which may include error information if the request was unsuccessful
2409
         */
2410
        void onCompleted(List<GraphPlace> places, Response response);
2411
    }
2412
 
2413
    private static class ParcelFileDescriptorWithMimeType implements Parcelable {
2414
        private final String mimeType;
2415
        private final ParcelFileDescriptor fileDescriptor;
2416
 
2417
        public String getMimeType() {
2418
            return mimeType;
2419
        }
2420
 
2421
        public ParcelFileDescriptor getFileDescriptor() {
2422
            return fileDescriptor;
2423
        }
2424
 
2425
        public int describeContents() {
2426
            return CONTENTS_FILE_DESCRIPTOR;
2427
        }
2428
 
2429
        public void writeToParcel(Parcel out, int flags) {
2430
            out.writeString(mimeType);
2431
            out.writeFileDescriptor(fileDescriptor.getFileDescriptor());
2432
        }
2433
 
2434
        @SuppressWarnings("unused")
2435
        public static final Parcelable.Creator<ParcelFileDescriptorWithMimeType> CREATOR
2436
                = new Parcelable.Creator<ParcelFileDescriptorWithMimeType>() {
2437
            public ParcelFileDescriptorWithMimeType createFromParcel(Parcel in) {
2438
                return new ParcelFileDescriptorWithMimeType(in);
2439
            }
2440
 
2441
            public ParcelFileDescriptorWithMimeType[] newArray(int size) {
2442
                return new ParcelFileDescriptorWithMimeType[size];
2443
            }
2444
        };
2445
 
2446
        public ParcelFileDescriptorWithMimeType(ParcelFileDescriptor fileDescriptor, String mimeType) {
2447
            this.mimeType = mimeType;
2448
            this.fileDescriptor = fileDescriptor;
2449
        }
2450
 
2451
        private ParcelFileDescriptorWithMimeType(Parcel in) {
2452
            mimeType = in.readString();
2453
            fileDescriptor = in.readFileDescriptor();
2454
        }
2455
    }
2456
}