react-native-update 10.52.3 → 10.53.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +3 -3
- package/android/src/main/AndroidManifest.xml +3 -9
- package/android/src/main/AndroidManifestNew.xml +3 -9
- package/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java +280 -0
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/ErrorCodes.java +2 -0
- package/android/src/main/java/cn/reactnative/modules/update/PackageInstallerStatusReceiver.java +12 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +1 -8
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +23 -13
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleSupport.java +0 -30
- package/cpp/patch_core/error_codes.h +6 -0
- package/package.json +1 -1
- package/src/client.ts +0 -22
- package/src/error.ts +5 -1
- package/android/src/main/java/cn/reactnative/modules/update/PushyFileProvider.java +0 -14
- package/android/src/main/res/xml/pushy_file_paths.xml +0 -5
- package/src/permissions.native.ts +0 -1
- package/src/permissions.ts +0 -8
package/android/build.gradle
CHANGED
|
@@ -125,10 +125,10 @@ android {
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
|
-
compileSdkVersion safeExtGet('compileSdkVersion',
|
|
129
|
-
buildToolsVersion safeExtGet('buildToolsVersion', '
|
|
128
|
+
compileSdkVersion safeExtGet('compileSdkVersion', 31)
|
|
129
|
+
buildToolsVersion safeExtGet('buildToolsVersion', '31.0.0')
|
|
130
130
|
defaultConfig {
|
|
131
|
-
minSdkVersion safeExtGet('minSdkVersion',
|
|
131
|
+
minSdkVersion safeExtGet('minSdkVersion', 21)
|
|
132
132
|
targetSdkVersion safeExtGet('targetSdkVersion', 27)
|
|
133
133
|
versionCode 1
|
|
134
134
|
versionName "1.81.4"
|
|
@@ -2,14 +2,8 @@
|
|
|
2
2
|
package="cn.reactnative.modules.update">
|
|
3
3
|
<application>
|
|
4
4
|
<meta-data android:name="pushy_build_time" android:value="@string/pushy_build_time" />
|
|
5
|
-
<
|
|
6
|
-
android:name=".
|
|
7
|
-
android:
|
|
8
|
-
android:exported="false"
|
|
9
|
-
android:grantUriPermissions="true">
|
|
10
|
-
<meta-data
|
|
11
|
-
android:name="android.support.FILE_PROVIDER_PATHS"
|
|
12
|
-
android:resource="@xml/pushy_file_paths" />
|
|
13
|
-
</provider>
|
|
5
|
+
<receiver
|
|
6
|
+
android:name="cn.reactnative.modules.update.PackageInstallerStatusReceiver"
|
|
7
|
+
android:exported="false" />
|
|
14
8
|
</application>
|
|
15
9
|
</manifest>
|
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
2
|
<application>
|
|
3
3
|
<meta-data android:name="pushy_build_time" android:value="@string/pushy_build_time" />
|
|
4
|
-
<
|
|
5
|
-
android:name=".
|
|
6
|
-
android:
|
|
7
|
-
android:exported="false"
|
|
8
|
-
android:grantUriPermissions="true">
|
|
9
|
-
<meta-data
|
|
10
|
-
android:name="android.support.FILE_PROVIDER_PATHS"
|
|
11
|
-
android:resource="@xml/pushy_file_paths" />
|
|
12
|
-
</provider>
|
|
4
|
+
<receiver
|
|
5
|
+
android:name="cn.reactnative.modules.update.PackageInstallerStatusReceiver"
|
|
6
|
+
android:exported="false" />
|
|
13
7
|
</application>
|
|
14
8
|
</manifest>
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
package cn.reactnative.modules.update;
|
|
2
|
+
|
|
3
|
+
import android.Manifest;
|
|
4
|
+
import android.app.PendingIntent;
|
|
5
|
+
import android.content.Context;
|
|
6
|
+
import android.content.Intent;
|
|
7
|
+
import android.content.pm.PackageInfo;
|
|
8
|
+
import android.content.pm.PackageInstaller;
|
|
9
|
+
import android.content.pm.PackageManager;
|
|
10
|
+
import android.net.Uri;
|
|
11
|
+
import android.os.Build;
|
|
12
|
+
import android.os.Process;
|
|
13
|
+
import android.provider.Settings;
|
|
14
|
+
import android.util.Log;
|
|
15
|
+
import com.facebook.react.bridge.Promise;
|
|
16
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
17
|
+
import java.io.File;
|
|
18
|
+
import java.io.FileInputStream;
|
|
19
|
+
import java.io.OutputStream;
|
|
20
|
+
import java.util.concurrent.ConcurrentHashMap;
|
|
21
|
+
|
|
22
|
+
final class ApkInstaller {
|
|
23
|
+
private static final int COPY_BUFFER_SIZE = 64 * 1024;
|
|
24
|
+
private static final String SESSION_ID_EXTRA =
|
|
25
|
+
"cn.reactnative.modules.update.extra.PACKAGE_INSTALLER_SESSION_ID";
|
|
26
|
+
private static final ConcurrentHashMap<Integer, Promise> pendingPromises =
|
|
27
|
+
new ConcurrentHashMap<Integer, Promise>();
|
|
28
|
+
|
|
29
|
+
private ApkInstaller() {
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Everything in here runs inside the module call, where a thrown exception
|
|
34
|
+
* would take the whole app down instead of rejecting the promise. A missing
|
|
35
|
+
* REQUEST_INSTALL_PACKAGES declaration must surface as a rejection the JS
|
|
36
|
+
* layer can report — never as a crash — so no failure is allowed to escape.
|
|
37
|
+
*/
|
|
38
|
+
static boolean ensureInstallPermission(
|
|
39
|
+
ReactApplicationContext reactContext,
|
|
40
|
+
Promise promise
|
|
41
|
+
) {
|
|
42
|
+
try {
|
|
43
|
+
return checkInstallPermission(reactContext, promise);
|
|
44
|
+
} catch (Throwable error) {
|
|
45
|
+
promise.reject(
|
|
46
|
+
ErrorCodes.APK_INSTALL_PERMISSION_REQUIRED,
|
|
47
|
+
"Unable to verify the install-packages permission",
|
|
48
|
+
error
|
|
49
|
+
);
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private static boolean checkInstallPermission(
|
|
55
|
+
ReactApplicationContext reactContext,
|
|
56
|
+
Promise promise
|
|
57
|
+
) {
|
|
58
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
|
59
|
+
promise.reject(
|
|
60
|
+
ErrorCodes.UNSUPPORTED_PLATFORM,
|
|
61
|
+
"APK installation requires Android 5.0 or newer"
|
|
62
|
+
);
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
PackageManager packageManager = reactContext.getPackageManager();
|
|
71
|
+
// canRequestPackageInstalls() itself throws SecurityException when the
|
|
72
|
+
// permission is not declared, so the manifest check has to come first.
|
|
73
|
+
if (!declaresInstallPermission(reactContext, packageManager)) {
|
|
74
|
+
promise.reject(
|
|
75
|
+
ErrorCodes.APK_INSTALL_PERMISSION_REQUIRED,
|
|
76
|
+
"Declare android.permission.REQUEST_INSTALL_PACKAGES in AndroidManifest.xml"
|
|
77
|
+
);
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
if (packageManager.canRequestPackageInstalls()) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
Intent settingsIntent = new Intent(
|
|
87
|
+
Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,
|
|
88
|
+
Uri.parse("package:" + reactContext.getPackageName())
|
|
89
|
+
);
|
|
90
|
+
settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
91
|
+
reactContext.startActivity(settingsIntent);
|
|
92
|
+
promise.reject(
|
|
93
|
+
ErrorCodes.APK_INSTALL_PERMISSION_REQUIRED,
|
|
94
|
+
"Allow this app to install unknown apps in system settings, then retry"
|
|
95
|
+
);
|
|
96
|
+
} catch (Throwable error) {
|
|
97
|
+
promise.reject(
|
|
98
|
+
ErrorCodes.APK_INSTALL_PERMISSION_REQUIRED,
|
|
99
|
+
"Unable to open the unknown app sources settings",
|
|
100
|
+
error
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private static boolean declaresInstallPermission(
|
|
107
|
+
Context context,
|
|
108
|
+
PackageManager packageManager
|
|
109
|
+
) {
|
|
110
|
+
try {
|
|
111
|
+
PackageInfo packageInfo = packageManager.getPackageInfo(
|
|
112
|
+
context.getPackageName(),
|
|
113
|
+
PackageManager.GET_PERMISSIONS
|
|
114
|
+
);
|
|
115
|
+
String[] requestedPermissions = packageInfo.requestedPermissions;
|
|
116
|
+
if (requestedPermissions == null) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
for (String permission : requestedPermissions) {
|
|
120
|
+
if (Manifest.permission.REQUEST_INSTALL_PACKAGES.equals(permission)) {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
} catch (Throwable error) {
|
|
125
|
+
// A package manager that cannot answer is treated as "not declared":
|
|
126
|
+
// the caller rejects with a code the app can act on.
|
|
127
|
+
Log.e(UpdateContext.TAG, "Unable to inspect requested permissions", error);
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
static void install(
|
|
133
|
+
ReactApplicationContext reactContext,
|
|
134
|
+
File apkFile,
|
|
135
|
+
String sourceUrl,
|
|
136
|
+
Promise promise
|
|
137
|
+
) {
|
|
138
|
+
PackageInstaller packageInstaller = null;
|
|
139
|
+
int sessionId = -1;
|
|
140
|
+
boolean committed = false;
|
|
141
|
+
try {
|
|
142
|
+
if (!apkFile.isFile() || apkFile.length() <= 0) {
|
|
143
|
+
promise.reject(ErrorCodes.APK_INSTALL_FAILED, "Downloaded APK is empty or missing");
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
packageInstaller = reactContext.getPackageManager().getPackageInstaller();
|
|
148
|
+
PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
|
|
149
|
+
PackageInstaller.SessionParams.MODE_FULL_INSTALL
|
|
150
|
+
);
|
|
151
|
+
sessionParams.setAppPackageName(reactContext.getPackageName());
|
|
152
|
+
sessionParams.setSize(apkFile.length());
|
|
153
|
+
if (sourceUrl != null && !sourceUrl.isEmpty()) {
|
|
154
|
+
sessionParams.setOriginatingUri(Uri.parse(sourceUrl));
|
|
155
|
+
}
|
|
156
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
157
|
+
sessionParams.setOriginatingUid(Process.myUid());
|
|
158
|
+
}
|
|
159
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
160
|
+
sessionParams.setInstallReason(PackageManager.INSTALL_REASON_USER);
|
|
161
|
+
}
|
|
162
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
163
|
+
sessionParams.setRequireUserAction(
|
|
164
|
+
PackageInstaller.SessionParams.USER_ACTION_REQUIRED
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
sessionId = packageInstaller.createSession(sessionParams);
|
|
169
|
+
try (
|
|
170
|
+
PackageInstaller.Session session = packageInstaller.openSession(sessionId);
|
|
171
|
+
FileInputStream input = new FileInputStream(apkFile);
|
|
172
|
+
OutputStream output = session.openWrite("base.apk", 0, apkFile.length())
|
|
173
|
+
) {
|
|
174
|
+
byte[] buffer = new byte[COPY_BUFFER_SIZE];
|
|
175
|
+
int count;
|
|
176
|
+
while ((count = input.read(buffer)) != -1) {
|
|
177
|
+
output.write(buffer, 0, count);
|
|
178
|
+
}
|
|
179
|
+
session.fsync(output);
|
|
180
|
+
|
|
181
|
+
Intent statusIntent = new Intent(
|
|
182
|
+
reactContext,
|
|
183
|
+
PackageInstallerStatusReceiver.class
|
|
184
|
+
);
|
|
185
|
+
statusIntent.putExtra(SESSION_ID_EXTRA, sessionId);
|
|
186
|
+
int pendingIntentFlags = PendingIntent.FLAG_UPDATE_CURRENT;
|
|
187
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
188
|
+
pendingIntentFlags |= PendingIntent.FLAG_MUTABLE;
|
|
189
|
+
}
|
|
190
|
+
PendingIntent statusPendingIntent = PendingIntent.getBroadcast(
|
|
191
|
+
reactContext,
|
|
192
|
+
sessionId,
|
|
193
|
+
statusIntent,
|
|
194
|
+
pendingIntentFlags
|
|
195
|
+
);
|
|
196
|
+
pendingPromises.put(sessionId, promise);
|
|
197
|
+
session.commit(statusPendingIntent.getIntentSender());
|
|
198
|
+
committed = true;
|
|
199
|
+
}
|
|
200
|
+
} catch (Throwable error) {
|
|
201
|
+
if (committed) {
|
|
202
|
+
// The session is already in the system's hands (only session
|
|
203
|
+
// close failed): the status receiver owns the promise now, so
|
|
204
|
+
// settling here would be a second, contradictory outcome.
|
|
205
|
+
Log.w(UpdateContext.TAG, "Install session reported an error after commit", error);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (sessionId != -1) {
|
|
209
|
+
pendingPromises.remove(sessionId);
|
|
210
|
+
if (packageInstaller != null) {
|
|
211
|
+
try {
|
|
212
|
+
packageInstaller.abandonSession(sessionId);
|
|
213
|
+
} catch (Throwable abandonError) {
|
|
214
|
+
Log.w(UpdateContext.TAG, "Unable to abandon failed install session", abandonError);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
promise.reject(ErrorCodes.APK_INSTALL_FAILED, "Unable to stage APK installation", error);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
static void handleStatus(Context context, Intent intent) {
|
|
223
|
+
int sessionId = intent.getIntExtra(SESSION_ID_EXTRA, -1);
|
|
224
|
+
int status = intent.getIntExtra(
|
|
225
|
+
PackageInstaller.EXTRA_STATUS,
|
|
226
|
+
PackageInstaller.STATUS_FAILURE
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
if (status == PackageInstaller.STATUS_PENDING_USER_ACTION) {
|
|
230
|
+
Intent confirmationIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT);
|
|
231
|
+
if (confirmationIntent == null) {
|
|
232
|
+
rejectPending(sessionId, "System installer did not provide a confirmation screen");
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
confirmationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
237
|
+
context.startActivity(confirmationIntent);
|
|
238
|
+
resolvePending(sessionId);
|
|
239
|
+
} catch (Throwable error) {
|
|
240
|
+
rejectPending(sessionId, "Unable to open the system install confirmation", error);
|
|
241
|
+
}
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (status == PackageInstaller.STATUS_SUCCESS) {
|
|
246
|
+
resolvePending(sessionId);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
String statusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
|
|
251
|
+
rejectPending(
|
|
252
|
+
sessionId,
|
|
253
|
+
statusMessage == null ? "Package installation failed" : statusMessage
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private static void resolvePending(int sessionId) {
|
|
258
|
+
Promise promise = pendingPromises.remove(sessionId);
|
|
259
|
+
if (promise != null) {
|
|
260
|
+
promise.resolve(null);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private static void rejectPending(int sessionId, String message) {
|
|
265
|
+
rejectPending(sessionId, message, null);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private static void rejectPending(int sessionId, String message, Throwable error) {
|
|
269
|
+
Promise promise = pendingPromises.remove(sessionId);
|
|
270
|
+
if (promise != null) {
|
|
271
|
+
if (error == null) {
|
|
272
|
+
promise.reject(ErrorCodes.APK_INSTALL_FAILED, message);
|
|
273
|
+
} else {
|
|
274
|
+
promise.reject(ErrorCodes.APK_INSTALL_FAILED, message, error);
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
Log.e(UpdateContext.TAG, message, error);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
@@ -752,7 +752,7 @@ class DownloadTask implements Runnable {
|
|
|
752
752
|
|
|
753
753
|
// The task itself succeeded. Run the completion callback outside the
|
|
754
754
|
// try/catch above so an exception thrown by the callback (e.g. a
|
|
755
|
-
//
|
|
755
|
+
// PackageInstaller failure during APK staging) is not mistaken for
|
|
756
756
|
// a download failure that deletes the successfully downloaded file and
|
|
757
757
|
// settles the promise a second time.
|
|
758
758
|
if (params.listener != null) {
|
|
@@ -19,6 +19,8 @@ final class ErrorCodes {
|
|
|
19
19
|
static final String RESET_FAILED = "RESET_FAILED";
|
|
20
20
|
static final String INVALID_HASH_INFO = "INVALID_HASH_INFO";
|
|
21
21
|
static final String UNSUPPORTED_PLATFORM = "UNSUPPORTED_PLATFORM";
|
|
22
|
+
static final String APK_INSTALL_PERMISSION_REQUIRED = "APK_INSTALL_PERMISSION_REQUIRED";
|
|
23
|
+
static final String APK_INSTALL_FAILED = "APK_INSTALL_FAILED";
|
|
22
24
|
|
|
23
25
|
private ErrorCodes() {
|
|
24
26
|
}
|
package/android/src/main/java/cn/reactnative/modules/update/PackageInstallerStatusReceiver.java
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
package cn.reactnative.modules.update;
|
|
2
|
+
|
|
3
|
+
import android.content.BroadcastReceiver;
|
|
4
|
+
import android.content.Context;
|
|
5
|
+
import android.content.Intent;
|
|
6
|
+
|
|
7
|
+
public final class PackageInstallerStatusReceiver extends BroadcastReceiver {
|
|
8
|
+
@Override
|
|
9
|
+
public void onReceive(Context context, Intent intent) {
|
|
10
|
+
ApkInstaller.handleStatus(context, intent);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -5,7 +5,6 @@ import android.content.SharedPreferences;
|
|
|
5
5
|
import android.content.pm.PackageInfo;
|
|
6
6
|
import android.content.pm.PackageManager;
|
|
7
7
|
import android.os.Build;
|
|
8
|
-
import android.os.Environment;
|
|
9
8
|
import android.util.Log;
|
|
10
9
|
import com.facebook.react.ReactInstanceManager;
|
|
11
10
|
import java.io.File;
|
|
@@ -281,13 +280,7 @@ public class UpdateContext {
|
|
|
281
280
|
params.hash = hash;
|
|
282
281
|
params.listener = listener;
|
|
283
282
|
|
|
284
|
-
|
|
285
|
-
params.targetFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "pushy_update.apk");
|
|
286
|
-
|
|
287
|
-
} else {
|
|
288
|
-
params.targetFile = new File(rootDir, fileName);
|
|
289
|
-
|
|
290
|
-
}
|
|
283
|
+
params.targetFile = new File(rootDir, fileName);
|
|
291
284
|
// params.unzipDirectory = new File(rootDir, hash);
|
|
292
285
|
enqueue(params);
|
|
293
286
|
}
|
|
@@ -61,21 +61,31 @@ public class UpdateModuleImpl {
|
|
|
61
61
|
final ReadableMap options,
|
|
62
62
|
final Promise promise
|
|
63
63
|
) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
// A module call runs on the native modules thread: anything thrown here
|
|
65
|
+
// reaches the bridge's exception handler and kills the app. Every
|
|
66
|
+
// failure of this path — a missing REQUEST_INSTALL_PACKAGES declaration
|
|
67
|
+
// above all — has to come back as a promise rejection instead.
|
|
68
|
+
try {
|
|
69
|
+
final String url = options.getString("url");
|
|
70
|
+
String hash = options.getString("hash");
|
|
71
|
+
String target = options.getString("target");
|
|
72
|
+
if (!ApkInstaller.ensureInstallPermission(reactContext, promise)) {
|
|
73
|
+
return;
|
|
72
74
|
}
|
|
75
|
+
updateContext.downloadFile(url, hash, target, new UpdateContext.DownloadFileListener() {
|
|
76
|
+
@Override
|
|
77
|
+
public void onDownloadCompleted(DownloadTaskParams params) {
|
|
78
|
+
ApkInstaller.install(reactContext, params.targetFile, url, promise);
|
|
79
|
+
}
|
|
73
80
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
@Override
|
|
82
|
+
public void onDownloadFailed(Throwable error) {
|
|
83
|
+
promise.reject(ErrorCodes.DOWNLOAD_FAILED, error);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
} catch (Throwable error) {
|
|
87
|
+
promise.reject(ErrorCodes.APK_INSTALL_FAILED, error);
|
|
88
|
+
}
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
public static void downloadPatchFromPackage(
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
package cn.reactnative.modules.update;
|
|
2
2
|
|
|
3
|
-
import static androidx.core.content.FileProvider.getUriForFile;
|
|
4
|
-
|
|
5
|
-
import android.content.Intent;
|
|
6
|
-
import android.net.Uri;
|
|
7
|
-
import android.os.Build;
|
|
8
3
|
import android.util.Log;
|
|
9
|
-
import com.facebook.react.bridge.ReactApplicationContext;
|
|
10
|
-
import java.io.File;
|
|
11
4
|
import java.util.HashMap;
|
|
12
5
|
import java.util.Map;
|
|
13
6
|
|
|
@@ -48,27 +41,4 @@ final class UpdateModuleSupport {
|
|
|
48
41
|
constants.put("supportedDiffVersion", supportedDiffVersion);
|
|
49
42
|
return constants;
|
|
50
43
|
}
|
|
51
|
-
|
|
52
|
-
static void installApk(ReactApplicationContext reactContext, File toInstall) {
|
|
53
|
-
Uri apkUri;
|
|
54
|
-
Intent intent;
|
|
55
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
56
|
-
apkUri = getUriForFile(
|
|
57
|
-
reactContext,
|
|
58
|
-
reactContext.getPackageName() + ".pushy.fileprovider",
|
|
59
|
-
toInstall
|
|
60
|
-
);
|
|
61
|
-
intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
|
|
62
|
-
intent.setData(apkUri);
|
|
63
|
-
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
64
|
-
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
65
|
-
} else {
|
|
66
|
-
apkUri = Uri.fromFile(toInstall);
|
|
67
|
-
intent = new Intent(Intent.ACTION_VIEW);
|
|
68
|
-
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
|
|
69
|
-
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
reactContext.startActivity(intent);
|
|
73
|
-
}
|
|
74
44
|
}
|
|
@@ -39,6 +39,12 @@ constexpr const char* kInvalidHashInfo = "INVALID_HASH_INFO";
|
|
|
39
39
|
// The method is not supported on this platform (e.g. downloadAndInstallApk
|
|
40
40
|
// outside Android).
|
|
41
41
|
constexpr const char* kUnsupportedPlatform = "UNSUPPORTED_PLATFORM";
|
|
42
|
+
// Android full-package installation requires the user to trust the app as an
|
|
43
|
+
// install source before PackageInstaller can accept a session.
|
|
44
|
+
constexpr const char* kApkInstallPermissionRequired =
|
|
45
|
+
"APK_INSTALL_PERMISSION_REQUIRED";
|
|
46
|
+
// PackageInstaller could not create, write, commit, or continue an APK session.
|
|
47
|
+
constexpr const char* kApkInstallFailed = "APK_INSTALL_FAILED";
|
|
42
48
|
|
|
43
49
|
} // namespace error_codes
|
|
44
50
|
} // namespace pushy
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -26,7 +26,6 @@ import {
|
|
|
26
26
|
type UpdateErrorCode,
|
|
27
27
|
} from './error';
|
|
28
28
|
import i18n from './i18n';
|
|
29
|
-
import { PermissionsAndroid } from './permissions';
|
|
30
29
|
import {
|
|
31
30
|
resolveServerEventHash,
|
|
32
31
|
resolveServerEventType,
|
|
@@ -1302,27 +1301,6 @@ export class Pushy {
|
|
|
1302
1301
|
this.throwIfEnabled(err);
|
|
1303
1302
|
return;
|
|
1304
1303
|
}
|
|
1305
|
-
if (Platform.Version <= 23) {
|
|
1306
|
-
try {
|
|
1307
|
-
const granted = await PermissionsAndroid.request(
|
|
1308
|
-
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE
|
|
1309
|
-
);
|
|
1310
|
-
if (granted !== PermissionsAndroid.RESULTS.GRANTED) {
|
|
1311
|
-
const err = new UpdateError(
|
|
1312
|
-
this.t('error_storage_permission_rejected'),
|
|
1313
|
-
'STORAGE_PERMISSION_REJECTED'
|
|
1314
|
-
);
|
|
1315
|
-
this.emitError(err, 'rejectStoragePermission');
|
|
1316
|
-
this.throwIfEnabled(err);
|
|
1317
|
-
return;
|
|
1318
|
-
}
|
|
1319
|
-
} catch (e: any) {
|
|
1320
|
-
const err = toUpdateError(e, 'STORAGE_PERMISSION_ERROR');
|
|
1321
|
-
this.emitError(err, 'errorStoragePermission');
|
|
1322
|
-
this.throwIfEnabled(err);
|
|
1323
|
-
return;
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
1304
|
sharedState.apkStatus = 'downloading';
|
|
1327
1305
|
this.report({ type: 'downloadingApk' });
|
|
1328
1306
|
const progressKey = 'downloadingApk';
|
package/src/error.ts
CHANGED
|
@@ -34,7 +34,9 @@ export type UpdateErrorCode =
|
|
|
34
34
|
| 'RESTART_FAILED'
|
|
35
35
|
| 'RESET_FAILED'
|
|
36
36
|
| 'INVALID_HASH_INFO'
|
|
37
|
-
| 'UNSUPPORTED_PLATFORM'
|
|
37
|
+
| 'UNSUPPORTED_PLATFORM'
|
|
38
|
+
| 'APK_INSTALL_PERMISSION_REQUIRED'
|
|
39
|
+
| 'APK_INSTALL_FAILED';
|
|
38
40
|
|
|
39
41
|
const KNOWN_CODES = new Set<string>([
|
|
40
42
|
'MODULE_NOT_LOADED',
|
|
@@ -58,6 +60,8 @@ const KNOWN_CODES = new Set<string>([
|
|
|
58
60
|
'RESET_FAILED',
|
|
59
61
|
'INVALID_HASH_INFO',
|
|
60
62
|
'UNSUPPORTED_PLATFORM',
|
|
63
|
+
'APK_INSTALL_PERMISSION_REQUIRED',
|
|
64
|
+
'APK_INSTALL_FAILED',
|
|
61
65
|
]);
|
|
62
66
|
|
|
63
67
|
/**
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
package cn.reactnative.modules.update;
|
|
2
|
-
|
|
3
|
-
import androidx.core.content.FileProvider;
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Providing a custom {@code FileProvider} prevents manifest {@code <provider>} name collisions.
|
|
7
|
-
*
|
|
8
|
-
* See https://developer.android.com/guide/topics/manifest/provider-element.html for details.
|
|
9
|
-
*/
|
|
10
|
-
public class PushyFileProvider extends FileProvider {
|
|
11
|
-
|
|
12
|
-
// This class intentionally left blank.
|
|
13
|
-
|
|
14
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { PermissionsAndroid } from 'react-native';
|
package/src/permissions.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
// RN >= 0.87 (generated types) no longer exports the `PermissionsAndroidStatic`
|
|
2
|
-
// interface, so derive the shape from the exported value instead — that spelling
|
|
3
|
-
// works on every RN version we support.
|
|
4
|
-
import type { PermissionsAndroid as RNPermissionsAndroid } from 'react-native';
|
|
5
|
-
import { emptyModule } from './utils';
|
|
6
|
-
|
|
7
|
-
export const PermissionsAndroid =
|
|
8
|
-
emptyModule as unknown as typeof RNPermissionsAndroid;
|