react-native-update 10.53.0 → 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.
|
@@ -29,9 +29,31 @@ final class ApkInstaller {
|
|
|
29
29
|
private ApkInstaller() {
|
|
30
30
|
}
|
|
31
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
|
+
*/
|
|
32
38
|
static boolean ensureInstallPermission(
|
|
33
39
|
ReactApplicationContext reactContext,
|
|
34
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
|
|
35
57
|
) {
|
|
36
58
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
|
37
59
|
promise.reject(
|
|
@@ -46,6 +68,8 @@ final class ApkInstaller {
|
|
|
46
68
|
}
|
|
47
69
|
|
|
48
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.
|
|
49
73
|
if (!declaresInstallPermission(reactContext, packageManager)) {
|
|
50
74
|
promise.reject(
|
|
51
75
|
ErrorCodes.APK_INSTALL_PERMISSION_REQUIRED,
|
|
@@ -97,7 +121,9 @@ final class ApkInstaller {
|
|
|
97
121
|
return true;
|
|
98
122
|
}
|
|
99
123
|
}
|
|
100
|
-
} catch (
|
|
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.
|
|
101
127
|
Log.e(UpdateContext.TAG, "Unable to inspect requested permissions", error);
|
|
102
128
|
}
|
|
103
129
|
return false;
|
|
@@ -109,37 +135,36 @@ final class ApkInstaller {
|
|
|
109
135
|
String sourceUrl,
|
|
110
136
|
Promise promise
|
|
111
137
|
) {
|
|
112
|
-
|
|
113
|
-
promise.reject(ErrorCodes.APK_INSTALL_FAILED, "Downloaded APK is empty or missing");
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
PackageInstaller packageInstaller = reactContext
|
|
118
|
-
.getPackageManager()
|
|
119
|
-
.getPackageInstaller();
|
|
120
|
-
PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
|
|
121
|
-
PackageInstaller.SessionParams.MODE_FULL_INSTALL
|
|
122
|
-
);
|
|
123
|
-
sessionParams.setAppPackageName(reactContext.getPackageName());
|
|
124
|
-
sessionParams.setSize(apkFile.length());
|
|
125
|
-
if (sourceUrl != null && !sourceUrl.isEmpty()) {
|
|
126
|
-
sessionParams.setOriginatingUri(Uri.parse(sourceUrl));
|
|
127
|
-
}
|
|
128
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
129
|
-
sessionParams.setOriginatingUid(Process.myUid());
|
|
130
|
-
}
|
|
131
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
132
|
-
sessionParams.setInstallReason(PackageManager.INSTALL_REASON_USER);
|
|
133
|
-
}
|
|
134
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
135
|
-
sessionParams.setRequireUserAction(
|
|
136
|
-
PackageInstaller.SessionParams.USER_ACTION_REQUIRED
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
|
|
138
|
+
PackageInstaller packageInstaller = null;
|
|
140
139
|
int sessionId = -1;
|
|
141
140
|
boolean committed = false;
|
|
142
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
|
+
|
|
143
168
|
sessionId = packageInstaller.createSession(sessionParams);
|
|
144
169
|
try (
|
|
145
170
|
PackageInstaller.Session session = packageInstaller.openSession(sessionId);
|
|
@@ -173,9 +198,16 @@ final class ApkInstaller {
|
|
|
173
198
|
committed = true;
|
|
174
199
|
}
|
|
175
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
|
+
}
|
|
176
208
|
if (sessionId != -1) {
|
|
177
209
|
pendingPromises.remove(sessionId);
|
|
178
|
-
if (
|
|
210
|
+
if (packageInstaller != null) {
|
|
179
211
|
try {
|
|
180
212
|
packageInstaller.abandonSession(sessionId);
|
|
181
213
|
} catch (Throwable abandonError) {
|
|
@@ -61,23 +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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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;
|
|
74
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
|
+
}
|
|
75
80
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
+
}
|
|
81
89
|
}
|
|
82
90
|
|
|
83
91
|
public static void downloadPatchFromPackage(
|