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.net.Uri;
20
import android.os.Bundle;
21
import bolts.AppLink;
22
import bolts.AppLinkResolver;
23
import bolts.Continuation;
24
import bolts.Task;
25
import com.facebook.model.GraphObject;
26
import org.json.JSONArray;
27
import org.json.JSONException;
28
import org.json.JSONObject;
29
 
30
import java.util.*;
31
 
32
/**
33
 * Provides an implementation for the {@link AppLinkResolver AppLinkResolver} interface that uses the Facebook App Link
34
 * index to solve App Links, given a Url. It also provides an additional helper method that can resolve multiple App
35
 * Links in a single call.
36
 */
37
public class FacebookAppLinkResolver implements AppLinkResolver {
38
 
39
    private static final String APP_LINK_KEY = "app_links";
40
    private static final String APP_LINK_ANDROID_TARGET_KEY = "android";
41
    private static final String APP_LINK_WEB_TARGET_KEY = "web";
42
    private static final String APP_LINK_TARGET_PACKAGE_KEY = "package";
43
    private static final String APP_LINK_TARGET_CLASS_KEY = "class";
44
    private static final String APP_LINK_TARGET_APP_NAME_KEY = "app_name";
45
    private static final String APP_LINK_TARGET_URL_KEY = "url";
46
    private static final String APP_LINK_TARGET_SHOULD_FALLBACK_KEY = "should_fallback";
47
 
48
    private final HashMap<Uri, AppLink> cachedAppLinks = new HashMap<Uri, AppLink>();
49
 
50
    /**
51
     * Asynchronously resolves App Link data for the passed in Uri
52
     *
53
     * @param uri Uri to be resolved into an App Link
54
     * @return A Task that, when successful, will return an AppLink for the passed in Uri. This may be null if no App
55
     * Link data was found for this Uri.
56
     * In the case of general server errors, the task will be completed with the corresponding error.
57
     */
58
    public Task<AppLink> getAppLinkFromUrlInBackground(final Uri uri) {
59
        ArrayList<Uri> uris = new ArrayList<Uri>();
60
        uris.add(uri);
61
 
62
        Task<Map<Uri, AppLink>> resolveTask = getAppLinkFromUrlsInBackground(uris);
63
 
64
        return resolveTask.onSuccess(new Continuation<Map<Uri, AppLink>, AppLink>() {
65
            @Override
66
            public AppLink then(Task<Map<Uri, AppLink>> resolveUrisTask) throws Exception {
67
                return resolveUrisTask.getResult().get(uri);
68
            }
69
        });
70
    }
71
 
72
    /**
73
     * Asynchronously resolves App Link data for multiple Urls
74
     *
75
     * @param uris A list of Uri objects to resolve into App Links
76
     * @return A Task that, when successful, will return a Map of Uri->AppLink for each Uri that was successfully
77
     * resolved into an App Link. Uris that could not be resolved into App Links will not be present in the Map.
78
     * In the case of general server errors, the task will be completed with the corresponding error.
79
     */
80
    public Task<Map<Uri, AppLink>> getAppLinkFromUrlsInBackground(List<Uri> uris) {
81
        final Map<Uri, AppLink> appLinkResults = new HashMap<Uri, AppLink>();
82
        final HashSet<Uri> urisToRequest = new HashSet<Uri>();
83
        StringBuilder graphRequestFields = new StringBuilder();
84
 
85
        for (Uri uri : uris) {
86
            AppLink appLink = null;
87
            synchronized (cachedAppLinks) {
88
                appLink = cachedAppLinks.get(uri);
89
            }
90
 
91
            if (appLink != null) {
92
                appLinkResults.put(uri, appLink);
93
            } else {
94
                if (!urisToRequest.isEmpty()) {
95
                    graphRequestFields.append(',');
96
                }
97
                graphRequestFields.append(uri.toString());
98
                urisToRequest.add(uri);
99
            }
100
        }
101
 
102
        if (urisToRequest.isEmpty()) {
103
            return Task.forResult(appLinkResults);
104
        }
105
 
106
        final Task<Map<Uri, AppLink>>.TaskCompletionSource taskCompletionSource = Task.create();
107
 
108
        Bundle appLinkRequestParameters = new Bundle();
109
 
110
        appLinkRequestParameters.putString("ids", graphRequestFields.toString());
111
        appLinkRequestParameters.putString(
112
                "fields",
113
                String.format("%s.fields(%s,%s)", APP_LINK_KEY, APP_LINK_ANDROID_TARGET_KEY, APP_LINK_WEB_TARGET_KEY));
114
 
115
 
116
        Request appLinkRequest = new Request(
117
                null, /* Session */
118
                "", /* Graph path */
119
                appLinkRequestParameters, /* Query parameters */
120
                null, /* HttpMethod */
121
                new Request.Callback() { /* Callback */
122
                    @Override
123
                    public void onCompleted(Response response) {
124
                        FacebookRequestError error = response.getError();
125
                        if (error != null) {
126
                            taskCompletionSource.setError(error.getException());
127
                            return;
128
                        }
129
 
130
                        GraphObject responseObject = response.getGraphObject();
131
                        JSONObject responseJson = responseObject != null ? responseObject.getInnerJSONObject() : null;
132
                        if (responseJson == null) {
133
                            taskCompletionSource.setResult(appLinkResults);
134
                            return;
135
                        }
136
 
137
                        for (Uri uri : urisToRequest) {
138
                            String uriString = uri.toString();
139
                            if (!responseJson.has(uriString)) {
140
                                continue;
141
                            }
142
 
143
                            JSONObject urlData = null;
144
                            try {
145
                                urlData = responseJson.getJSONObject(uri.toString());
146
                                JSONObject appLinkData = urlData.getJSONObject(APP_LINK_KEY);
147
 
148
                                JSONArray rawTargets = appLinkData.getJSONArray(APP_LINK_ANDROID_TARGET_KEY);
149
 
150
                                int targetsCount = rawTargets.length();
151
                                List<AppLink.Target> targets = new ArrayList<AppLink.Target>(targetsCount);
152
 
153
                                for (int i = 0; i < targetsCount; i++) {
154
                                    AppLink.Target target = getAndroidTargetFromJson(rawTargets.getJSONObject(i));
155
                                    if (target != null) {
156
                                        targets.add(target);
157
                                    }
158
                                }
159
 
160
                                Uri webFallbackUrl = getWebFallbackUriFromJson(uri, appLinkData);
161
                                AppLink appLink = new AppLink(uri, targets, webFallbackUrl);
162
 
163
                                appLinkResults.put(uri, appLink);
164
                                synchronized (cachedAppLinks) {
165
                                    cachedAppLinks.put(uri, appLink);
166
                                }
167
                            } catch (JSONException e) {
168
                                // The data for this uri was missing or badly formed.
169
                                continue;
170
                            }
171
                        }
172
 
173
                        taskCompletionSource.setResult(appLinkResults);
174
                    }
175
                });
176
 
177
        appLinkRequest.executeAsync();
178
 
179
        return taskCompletionSource.getTask();
180
    }
181
 
182
    private static AppLink.Target getAndroidTargetFromJson(JSONObject targetJson) {
183
        String packageName = tryGetStringFromJson(targetJson, APP_LINK_TARGET_PACKAGE_KEY, null);
184
        if (packageName == null) {
185
            // Package name is mandatory for each Android target
186
            return null;
187
        }
188
        String className = tryGetStringFromJson(targetJson, APP_LINK_TARGET_CLASS_KEY, null);
189
        String appName = tryGetStringFromJson(targetJson, APP_LINK_TARGET_APP_NAME_KEY, null);
190
        String targetUrlString = tryGetStringFromJson(targetJson, APP_LINK_TARGET_URL_KEY, null);
191
        Uri targetUri = null;
192
        if (targetUrlString != null) {
193
            targetUri = Uri.parse(targetUrlString);
194
        }
195
 
196
        return new AppLink.Target(packageName, className, targetUri, appName);
197
    }
198
 
199
    private static Uri getWebFallbackUriFromJson(Uri sourceUrl, JSONObject urlData) {
200
        // Try and get a web target. This is best effort. Any failures results in null being returned.
201
        try {
202
            JSONObject webTarget = urlData.getJSONObject(APP_LINK_WEB_TARGET_KEY);
203
            boolean shouldFallback = tryGetBooleanFromJson(webTarget, APP_LINK_TARGET_SHOULD_FALLBACK_KEY, true);
204
            if (!shouldFallback) {
205
                // Don't use a fallback url
206
                return null;
207
            }
208
 
209
            String webTargetUrlString = tryGetStringFromJson(webTarget, APP_LINK_TARGET_URL_KEY, null);
210
            Uri webUri = null;
211
            if (webTargetUrlString != null) {
212
                webUri = Uri.parse(webTargetUrlString);
213
            }
214
 
215
            // If we weren't able to parse a url from the web target, use the source url
216
            return webUri != null ? webUri: sourceUrl;
217
        } catch (JSONException e) {
218
            // If we were missing a web target, just use the source as the web url
219
            return sourceUrl;
220
        }
221
    }
222
 
223
    private static String tryGetStringFromJson(JSONObject json, String propertyName, String defaultValue) {
224
        try {
225
            return json.getString(propertyName);
226
        } catch(JSONException e) {
227
            return defaultValue;
228
        }
229
    }
230
 
231
    private static boolean tryGetBooleanFromJson(JSONObject json, String propertyName, boolean defaultValue) {
232
        try {
233
            return json.getBoolean(propertyName);
234
        } catch (JSONException e) {
235
            return defaultValue;
236
        }
237
    }
238
}