Skip to content

Commit 6dbf4ba

Browse files
natario1rogerhu
authored andcommitted
Push handling cleanup (#732)
* Refactor push handling * Delegate manifest parsing to handlers * Moved some logic for consistency * PushService tests
1 parent a69d994 commit 6dbf4ba

16 files changed

+648
-663
lines changed

Parse/src/main/java/com/parse/GCMService.java

Lines changed: 0 additions & 141 deletions
This file was deleted.
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
/*
2+
* Copyright (c) 2015-present, Parse, LLC.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
package com.parse;
10+
11+
import android.app.Service;
12+
import android.content.Context;
13+
import android.content.Intent;
14+
import android.support.annotation.NonNull;
15+
import android.support.annotation.Nullable;
16+
import android.support.annotation.WorkerThread;
17+
18+
import org.json.JSONException;
19+
import org.json.JSONObject;
20+
21+
import java.lang.ref.WeakReference;
22+
import java.util.concurrent.ExecutorService;
23+
import java.util.concurrent.Executors;
24+
25+
import bolts.Task;
26+
27+
/**
28+
* Proxy Service while running in GCM mode.
29+
*
30+
* We use an {@link ExecutorService} so that we can operate like a ghetto
31+
* {@link android.app.IntentService} where all incoming {@link Intent}s will be handled
32+
* sequentially.
33+
*/
34+
/** package */ class GcmPushHandler implements PushHandler {
35+
private static final String TAG = "GcmPushHandler";
36+
37+
static final String REGISTER_RESPONSE_ACTION = "com.google.android.c2dm.intent.REGISTRATION";
38+
static final String RECEIVE_PUSH_ACTION = "com.google.android.c2dm.intent.RECEIVE";
39+
40+
GcmPushHandler() {}
41+
42+
@NonNull
43+
@Override
44+
public SupportLevel isSupported() {
45+
if (!ManifestInfo.isGooglePlayServicesAvailable()) {
46+
return SupportLevel.MISSING_REQUIRED_DECLARATIONS;
47+
}
48+
return getManifestSupportLevel();
49+
}
50+
51+
private SupportLevel getManifestSupportLevel() {
52+
Context context = Parse.getApplicationContext();
53+
String[] requiredPermissions = new String[] {
54+
"android.permission.INTERNET",
55+
"android.permission.ACCESS_NETWORK_STATE",
56+
"android.permission.WAKE_LOCK",
57+
"com.google.android.c2dm.permission.RECEIVE",
58+
context.getPackageName() + ".permission.C2D_MESSAGE"
59+
};
60+
61+
if (!ManifestInfo.hasRequestedPermissions(context, requiredPermissions)) {
62+
return SupportLevel.MISSING_REQUIRED_DECLARATIONS;
63+
}
64+
65+
String packageName = context.getPackageName();
66+
String rcvrPermission = "com.google.android.c2dm.permission.SEND";
67+
Intent[] intents = new Intent[] {
68+
new Intent(GcmPushHandler.RECEIVE_PUSH_ACTION)
69+
.setPackage(packageName)
70+
.addCategory(packageName),
71+
new Intent(GcmPushHandler.REGISTER_RESPONSE_ACTION)
72+
.setPackage(packageName)
73+
.addCategory(packageName),
74+
};
75+
76+
if (!ManifestInfo.checkReceiver(GcmBroadcastReceiver.class, rcvrPermission, intents)) {
77+
return SupportLevel.MISSING_REQUIRED_DECLARATIONS;
78+
}
79+
80+
String[] optionalPermissions = new String[] {
81+
"android.permission.VIBRATE"
82+
};
83+
84+
if (!ManifestInfo.hasGrantedPermissions(context, optionalPermissions)) {
85+
return SupportLevel.MISSING_OPTIONAL_DECLARATIONS;
86+
}
87+
88+
return SupportLevel.SUPPORTED;
89+
}
90+
91+
@Nullable
92+
@Override
93+
public String getWarningMessage(SupportLevel level) {
94+
switch (level) {
95+
case SUPPORTED: return null;
96+
case MISSING_OPTIONAL_DECLARATIONS: return "Using GCM for Parse Push, " +
97+
"but the app manifest is missing some optional " +
98+
"declarations that should be added for maximum reliability. Please " +
99+
getWarningMessage();
100+
case MISSING_REQUIRED_DECLARATIONS:
101+
if (ManifestInfo.isGooglePlayServicesAvailable()) {
102+
return "Cannot use GCM for push because the app manifest is missing some " +
103+
"required declarations. Please " + getWarningMessage();
104+
} else {
105+
return "Cannot use GCM for push on this device because Google Play " +
106+
"Services is not available. Install Google Play Services from the Play Store.";
107+
}
108+
}
109+
return null;
110+
}
111+
112+
static String getWarningMessage() {
113+
String packageName = Parse.getApplicationContext().getPackageName();
114+
String gcmPackagePermission = packageName + ".permission.C2D_MESSAGE";
115+
return "make sure that these permissions are declared as children " +
116+
"of the root <manifest> element:\n" +
117+
"\n" +
118+
"<uses-permission android:name=\"android.permission.INTERNET\" />\n" +
119+
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n" +
120+
"<uses-permission android:name=\"android.permission.VIBRATE\" />\n" +
121+
"<uses-permission android:name=\"android.permission.WAKE_LOCK\" />\n" +
122+
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\" />\n" +
123+
"<uses-permission android:name=\"com.google.android.c2dm.permission.RECEIVE\" />\n" +
124+
"<permission android:name=\"" + gcmPackagePermission + "\" " +
125+
"android:protectionLevel=\"signature\" />\n" +
126+
"<uses-permission android:name=\"" + gcmPackagePermission + "\" />\n" +
127+
"\n" +
128+
"Also, please make sure that these services and broadcast receivers are declared as " +
129+
"children of the <application> element:\n" +
130+
"\n" +
131+
"<service android:name=\"com.parse.PushService\" />\n" +
132+
"<receiver android:name=\"com.parse.GcmBroadcastReceiver\" " +
133+
"android:permission=\"com.google.android.c2dm.permission.SEND\">\n" +
134+
" <intent-filter>\n" +
135+
" <action android:name=\"com.google.android.c2dm.intent.RECEIVE\" />\n" +
136+
" <action android:name=\"com.google.android.c2dm.intent.REGISTRATION\" />\n" +
137+
" <category android:name=\"" + packageName + "\" />\n" +
138+
" </intent-filter>\n" +
139+
"</receiver>\n" +
140+
"<receiver android:name=\"com.parse.ParsePushBroadcastReceiver\"" +
141+
" android:exported=false>\n" +
142+
" <intent-filter>\n" +
143+
" <action android:name=\"com.parse.push.intent.RECEIVE\" />\n" +
144+
" <action android:name=\"com.parse.push.intent.OPEN\" />\n" +
145+
" <action android:name=\"com.parse.push.intent.DELETE\" />\n" +
146+
" </intent-filter>\n" +
147+
"</receiver>";
148+
}
149+
150+
@Override
151+
public Task<Void> initialize() {
152+
return GcmRegistrar.getInstance().registerAsync();
153+
}
154+
155+
@WorkerThread
156+
@Override
157+
public void handlePush(Intent intent) {
158+
if (intent != null) {
159+
String action = intent.getAction();
160+
if (REGISTER_RESPONSE_ACTION.equals(action)) {
161+
handleGcmRegistrationIntent(intent);
162+
} else if (RECEIVE_PUSH_ACTION.equals(action)) {
163+
handleGcmPushIntent(intent);
164+
} else {
165+
PLog.e(TAG, "PushService got unknown intent in GCM mode: " + intent);
166+
}
167+
}
168+
}
169+
170+
@WorkerThread
171+
private void handleGcmRegistrationIntent(Intent intent) {
172+
try {
173+
// Have to block here since we are already in a background thread and as soon as we return,
174+
// PushService may exit.
175+
GcmRegistrar.getInstance().handleRegistrationIntentAsync(intent).waitForCompletion();
176+
} catch (InterruptedException e) {
177+
// do nothing
178+
}
179+
}
180+
181+
@WorkerThread
182+
private void handleGcmPushIntent(Intent intent) {
183+
String messageType = intent.getStringExtra("message_type");
184+
if (messageType != null) {
185+
/*
186+
* The GCM docs reserve the right to use the message_type field for new actions, but haven't
187+
* documented what those new actions are yet. For forwards compatibility, ignore anything
188+
* with a message_type field.
189+
*/
190+
PLog.i(TAG, "Ignored special message type " + messageType + " from GCM via intent " + intent);
191+
} else {
192+
String pushId = intent.getStringExtra("push_id");
193+
String timestamp = intent.getStringExtra("time");
194+
String dataString = intent.getStringExtra("data");
195+
String channel = intent.getStringExtra("channel");
196+
197+
JSONObject data = null;
198+
if (dataString != null) {
199+
try {
200+
data = new JSONObject(dataString);
201+
} catch (JSONException e) {
202+
PLog.e(TAG, "Ignoring push because of JSON exception while processing: " + dataString, e);
203+
return;
204+
}
205+
}
206+
207+
PushRouter.getInstance().handlePush(pushId, timestamp, channel, data);
208+
}
209+
}
210+
211+
}

Parse/src/main/java/com/parse/GcmRegistrar.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
import bolts.TaskCompletionSource;
3232

3333
/**
34-
* A class that manages registering for GCM and updating the registration if it is out of date.
34+
* A class that manages registering for GCM and updating the registration if it is out of date,
35+
* used by {@link com.parse.GcmPushHandler}.
3536
*/
3637
/** package */ class GcmRegistrar {
3738
private static final String TAG = "com.parse.GcmRegistrar";
@@ -181,7 +182,7 @@ public Void then(Task<String> task) {
181182
* Should be called by a broadcast receiver or service to handle the GCM registration response
182183
* intent (com.google.android.c2dm.intent.REGISTRATION).
183184
*/
184-
public Task<Void> handleRegistrationIntentAsync(Intent intent) {
185+
Task<Void> handleRegistrationIntentAsync(Intent intent) {
185186
List<Task<Void>> tasks = new ArrayList<>();
186187
/*
187188
* We have to parse the response here because GCM may send us a new registration_id

0 commit comments

Comments
 (0)