termux-app
573 строки · 27.6 Кб
1package com.termux.shared.android;
2
3import android.Manifest;
4import android.annotation.SuppressLint;
5import android.app.Activity;
6import android.app.Service;
7import android.content.Context;
8import android.content.Intent;
9import android.content.pm.PackageInfo;
10import android.content.pm.PackageManager;
11import android.net.Uri;
12import android.os.Build;
13import android.os.Environment;
14import android.os.PowerManager;
15import android.provider.Settings;
16
17import androidx.annotation.NonNull;
18import androidx.annotation.RequiresApi;
19import androidx.appcompat.app.AppCompatActivity;
20import androidx.core.content.ContextCompat;
21
22import com.google.common.base.Joiner;
23import com.termux.shared.R;
24import com.termux.shared.file.FileUtils;
25import com.termux.shared.logger.Logger;
26import com.termux.shared.errors.Error;
27import com.termux.shared.errors.FunctionErrno;
28import com.termux.shared.activity.ActivityUtils;
29
30import java.util.ArrayList;
31import java.util.Arrays;
32import java.util.Collections;
33import java.util.List;
34
35public class PermissionUtils {
36
37public static final int REQUEST_GRANT_STORAGE_PERMISSION = 1000;
38
39public static final int REQUEST_DISABLE_BATTERY_OPTIMIZATIONS = 2000;
40public static final int REQUEST_GRANT_DISPLAY_OVER_OTHER_APPS_PERMISSION = 2001;
41
42private static final String LOG_TAG = "PermissionUtils";
43
44
45/**
46* Check if app has been granted the required permission.
47*
48* @param context The context for operations.
49* @param permission The {@link String} name for permission to check.
50* @return Returns {@code true} if permission is granted, otherwise {@code false}.
51*/
52public static boolean checkPermission(@NonNull Context context, @NonNull String permission) {
53return checkPermissions(context, new String[]{permission});
54}
55
56/**
57* Check if app has been granted the required permissions.
58*
59* @param context The context for operations.
60* @param permissions The {@link String[]} names for permissions to check.
61* @return Returns {@code true} if permissions are granted, otherwise {@code false}.
62*/
63public static boolean checkPermissions(@NonNull Context context, @NonNull String[] permissions) {
64// checkSelfPermission may return true for permissions not even requested
65List<String> permissionsNotRequested = getPermissionsNotRequested(context, permissions);
66if (permissionsNotRequested.size() > 0) {
67Logger.logError(LOG_TAG,
68context.getString(R.string.error_attempted_to_check_for_permissions_not_requested,
69Joiner.on(", ").join(permissionsNotRequested)));
70return false;
71}
72
73int result;
74for (String permission : permissions) {
75result = ContextCompat.checkSelfPermission(context, permission);
76if (result != PackageManager.PERMISSION_GRANTED) {
77return false;
78}
79}
80
81return true;
82}
83
84
85
86/**
87* Request user to grant required permissions to the app.
88*
89* @param context The context for operations. It must be an instance of {@link Activity} or
90* {@link AppCompatActivity}.
91* @param permission The {@link String} name for permission to request.
92* @param requestCode The request code to use while asking for permission. It must be `>=0` or
93* will fail silently and will log an exception.
94* @return Returns {@code true} if requesting the permission was successful, otherwise {@code false}.
95*/
96@RequiresApi(api = Build.VERSION_CODES.M)
97public static boolean requestPermission(@NonNull Context context, @NonNull String permission,
98int requestCode) {
99return requestPermissions(context, new String[]{permission}, requestCode);
100}
101
102/**
103* Request user to grant required permissions to the app.
104*
105* On sdk 30 (android 11), Activity.onRequestPermissionsResult() will pass
106* {@link PackageManager#PERMISSION_DENIED} (-1) without asking the user for the permission
107* if user previously denied the permission prompt. On sdk 29 (android 10),
108* Activity.onRequestPermissionsResult() will pass {@link PackageManager#PERMISSION_DENIED} (-1)
109* without asking the user for the permission if user previously selected "Deny & don't ask again"
110* option in prompt. The user will have to manually enable permission in app info in Android
111* settings. If user grants and then denies in settings, then next time prompt will shown.
112*
113* @param context The context for operations. It must be an instance of {@link Activity} or
114* {@link AppCompatActivity}.
115* @param permissions The {@link String[]} names for permissions to request.
116* @param requestCode The request code to use while asking for permissions. It must be `>=0` or
117* will fail silently and will log an exception.
118* @return Returns {@code true} if requesting the permissions was successful, otherwise {@code false}.
119*/
120@RequiresApi(api = Build.VERSION_CODES.M)
121public static boolean requestPermissions(@NonNull Context context, @NonNull String[] permissions,
122int requestCode) {
123List<String> permissionsNotRequested = getPermissionsNotRequested(context, permissions);
124if (permissionsNotRequested.size() > 0) {
125Logger.logErrorAndShowToast(context, LOG_TAG,
126context.getString(R.string.error_attempted_to_ask_for_permissions_not_requested,
127Joiner.on(", ").join(permissionsNotRequested)));
128return false;
129}
130
131for (String permission : permissions) {
132int result = ContextCompat.checkSelfPermission(context, permission);
133// If at least one permission not granted
134if (result != PackageManager.PERMISSION_GRANTED) {
135Logger.logInfo(LOG_TAG, "Requesting Permissions: " + Arrays.toString(permissions));
136
137try {
138if (context instanceof AppCompatActivity)
139((AppCompatActivity) context).requestPermissions(permissions, requestCode);
140else if (context instanceof Activity)
141((Activity) context).requestPermissions(permissions, requestCode);
142else {
143Error.logErrorAndShowToast(context, LOG_TAG,
144FunctionErrno.ERRNO_PARAMETER_NOT_INSTANCE_OF.getError("context", "requestPermissions", "Activity or AppCompatActivity"));
145return false;
146}
147} catch (Exception e) {
148String errmsg = context.getString(R.string.error_failed_to_request_permissions, requestCode, Arrays.toString(permissions));
149Logger.logStackTraceWithMessage(LOG_TAG, errmsg, e);
150Logger.showToast(context, errmsg + "\n" + e.getMessage(), true);
151return false;
152}
153
154break;
155}
156}
157
158return true;
159}
160
161
162
163
164/**
165* Check if app has requested the required permission in the manifest.
166*
167* @param context The context for operations.
168* @param permission The {@link String} name for permission to check.
169* @return Returns {@code true} if permission has been requested, otherwise {@code false}.
170*/
171public static boolean isPermissionRequested(@NonNull Context context, @NonNull String permission) {
172return getPermissionsNotRequested(context, new String[]{permission}).size() == 0;
173}
174
175/**
176* Check if app has requested the required permissions or not in the manifest.
177*
178* @param context The context for operations.
179* @param permissions The {@link String[]} names for permissions to check.
180* @return Returns {@link List<String>} of permissions that have not been requested. It will have
181* size 0 if all permissions have been requested.
182*/
183@NonNull
184public static List<String> getPermissionsNotRequested(@NonNull Context context, @NonNull String[] permissions) {
185List<String> permissionsNotRequested = new ArrayList<>();
186Collections.addAll(permissionsNotRequested, permissions);
187
188PackageInfo packageInfo = PackageUtils.getPackageInfoForPackage(context, PackageManager.GET_PERMISSIONS);
189if (packageInfo == null) {
190return permissionsNotRequested;
191}
192
193// If no permissions are requested, then nothing to check
194if (packageInfo.requestedPermissions == null || packageInfo.requestedPermissions.length == 0)
195return permissionsNotRequested;
196
197List<String> requestedPermissionsList = Arrays.asList(packageInfo.requestedPermissions);
198for (String permission : permissions) {
199if (requestedPermissionsList.contains(permission)) {
200permissionsNotRequested.remove(permission);
201}
202}
203
204return permissionsNotRequested;
205}
206
207
208
209
210/** If path is under primary external storage directory and storage permission is missing,
211* then legacy or manage external storage permission will be requested from the user via a call
212* to {@link #checkAndRequestLegacyOrManageExternalStoragePermission(Context, int, boolean)}.
213*
214* @param context The context for operations.
215* @param filePath The path to check.
216* @param requestCode The request code to use while asking for permission.
217* @param showErrorMessage If an error message toast should be shown if permission is not granted.
218* @return Returns {@code true} if permission is granted, otherwise {@code false}.
219*/
220@SuppressLint("SdCardPath")
221public static boolean checkAndRequestLegacyOrManageExternalStoragePermissionIfPathOnPrimaryExternalStorage(
222@NonNull Context context, String filePath, int requestCode, boolean showErrorMessage) {
223// If path is under primary external storage directory, then check for missing permissions.
224if (!FileUtils.isPathInDirPaths(filePath,
225Arrays.asList(Environment.getExternalStorageDirectory().getAbsolutePath(), "/sdcard"), true))
226return true;
227
228return checkAndRequestLegacyOrManageExternalStoragePermission(context, requestCode, showErrorMessage);
229}
230
231/**
232* Check if legacy or manage external storage permissions has been granted. If
233* {@link #isLegacyExternalStoragePossible(Context)} returns {@code true}, them it will be
234* checked if app has has been granted {@link Manifest.permission#READ_EXTERNAL_STORAGE} and
235* {@link Manifest.permission#WRITE_EXTERNAL_STORAGE} permissions, otherwise it will be checked
236* if app has been granted the {@link Manifest.permission#MANAGE_EXTERNAL_STORAGE} permission.
237*
238* If storage permission is missing, it will be requested from the user if {@code context} is an
239* instance of {@link Activity} or {@link AppCompatActivity} and {@code requestCode}
240* is `>=0` and the function will automatically return. The caller should register for
241* Activity.onActivityResult() and Activity.onRequestPermissionsResult() and call this function
242* again but set {@code requestCode} to `-1` to check if permission was granted or not.
243*
244* Caller must add following to AndroidManifest.xml of the app, otherwise errors will be thrown.
245* {@code
246* <manifest
247* <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
248* <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
249* <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
250*
251* <application
252* android:requestLegacyExternalStorage="true"
253* ....
254* </application>
255* </manifest>
256*}
257* @param context The context for operations.
258* @param requestCode The request code to use while asking for permission.
259* @param showErrorMessage If an error message toast should be shown if permission is not granted.
260* @return Returns {@code true} if permission is granted, otherwise {@code false}.
261*/
262public static boolean checkAndRequestLegacyOrManageExternalStoragePermission(@NonNull Context context,
263int requestCode,
264boolean showErrorMessage) {
265String errmsg;
266boolean requestLegacyStoragePermission = isLegacyExternalStoragePossible(context);
267boolean checkIfHasRequestedLegacyExternalStorage = checkIfHasRequestedLegacyExternalStorage(context);
268
269if (requestLegacyStoragePermission && checkIfHasRequestedLegacyExternalStorage) {
270// Check if requestLegacyExternalStorage is set to true in app manifest
271if (!hasRequestedLegacyExternalStorage(context, showErrorMessage))
272return false;
273}
274
275if (checkStoragePermission(context, requestLegacyStoragePermission)) {
276return true;
277}
278
279
280errmsg = context.getString(R.string.msg_storage_permission_not_granted);
281Logger.logError(LOG_TAG, errmsg);
282if (showErrorMessage)
283Logger.showToast(context, errmsg, false);
284
285if (requestCode < 0 || Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
286return false;
287
288if (requestLegacyStoragePermission || Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
289requestLegacyStorageExternalPermission(context, requestCode);
290} else {
291requestManageStorageExternalPermission(context, requestCode);
292}
293
294return false;
295}
296
297/**
298* Check if app has been granted storage permission.
299*
300* @param context The context for operations.
301* @param checkLegacyStoragePermission If set to {@code true}, then it will be checked if app
302* has been granted {@link Manifest.permission#READ_EXTERNAL_STORAGE}
303* and {@link Manifest.permission#WRITE_EXTERNAL_STORAGE}
304* permissions, otherwise it will be checked if app has been
305* granted the {@link Manifest.permission#MANAGE_EXTERNAL_STORAGE}
306* permission.
307* @return Returns {@code true} if permission is granted, otherwise {@code false}.
308*/
309public static boolean checkStoragePermission(@NonNull Context context, boolean checkLegacyStoragePermission) {
310if (checkLegacyStoragePermission || Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
311return checkPermissions(context,
312new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
313Manifest.permission.WRITE_EXTERNAL_STORAGE});
314} else {
315return Environment.isExternalStorageManager();
316}
317}
318
319/**
320* Request user to grant {@link Manifest.permission#READ_EXTERNAL_STORAGE} and
321* {@link Manifest.permission#WRITE_EXTERNAL_STORAGE} permissions to the app.
322*
323* @param context The context for operations. It must be an instance of {@link Activity} or
324* {@link AppCompatActivity}.
325* @param requestCode The request code to use while asking for permission. It must be `>=0` or
326* will fail silently and will log an exception.
327* @return Returns {@code true} if requesting the permission was successful, otherwise {@code false}.
328*/
329@RequiresApi(api = Build.VERSION_CODES.M)
330public static boolean requestLegacyStorageExternalPermission(@NonNull Context context, int requestCode) {
331Logger.logInfo(LOG_TAG, "Requesting legacy external storage permission");
332return requestPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE, requestCode);
333}
334
335/** Wrapper for {@link #requestManageStorageExternalPermission(Context, int)}. */
336@RequiresApi(api = Build.VERSION_CODES.R)
337public static Error requestManageStorageExternalPermission(@NonNull Context context) {
338return requestManageStorageExternalPermission(context, -1);
339}
340
341/**
342* Request user to grant {@link Manifest.permission#MANAGE_EXTERNAL_STORAGE} permission to the app.
343*
344* @param context The context for operations, like an {@link Activity} or {@link Service} context.
345* It must be an instance of {@link Activity} or {@link AppCompatActivity} if
346* result is required via the Activity#onActivityResult() callback and
347* {@code requestCode} is `>=0`.
348* @param requestCode The request code to use while asking for permission. It must be `>=0` if
349* result it required.
350* @return Returns the {@code error} if requesting the permission was not successful, otherwise {@code null}.
351*/
352@RequiresApi(api = Build.VERSION_CODES.R)
353public static Error requestManageStorageExternalPermission(@NonNull Context context, int requestCode) {
354Logger.logInfo(LOG_TAG, "Requesting manage external storage permission");
355
356Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
357intent.addCategory("android.intent.category.DEFAULT");
358intent.setData(Uri.parse("package:" + context.getPackageName()));
359
360// Flag must not be passed for activity contexts, otherwise onActivityResult() will not be called with permission grant result.
361// Flag must be passed for non-activity contexts like services, otherwise "Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag" exception will be raised.
362if (!(context instanceof Activity))
363intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
364
365Error error;
366if (requestCode >=0)
367error = ActivityUtils.startActivityForResult(context, requestCode, intent, true, false);
368else
369error = ActivityUtils.startActivity(context, intent, true, false);
370
371// Use fallback if matching Activity did not exist for ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION.
372if (error != null) {
373intent = new Intent();
374intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
375if (requestCode >=0)
376return ActivityUtils.startActivityForResult(context, requestCode, intent);
377else
378return ActivityUtils.startActivity(context, intent);
379}
380
381return null;
382}
383
384/**
385* If app is targeting targetSdkVersion 30 (android 11) and running on sdk 30 (android 11) or
386* higher, then {@link android.R.attr#requestLegacyExternalStorage} attribute is ignored.
387* https://developer.android.com/training/data-storage/use-cases#opt-out-scoped-storage
388*/
389public static boolean isLegacyExternalStoragePossible(@NonNull Context context) {
390return !(Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
391PackageUtils.getTargetSDKForPackage(context) >= Build.VERSION_CODES.R);
392}
393
394/**
395* Return whether it should be checked if app has set
396* {@link android.R.attr#requestLegacyExternalStorage} attribute to {@code true}, if storage
397* permissions are to be requested based on if {@link #isLegacyExternalStoragePossible(Context)}
398* return {@code true}.
399*
400* If app is targeting targetSdkVersion 30 (android 11), then legacy storage can only be
401* requested if running on sdk 29 (android 10).
402* If app is targeting targetSdkVersion 29 (android 10), then legacy storage can only be
403* requested if running on sdk 29 (android 10) and higher.
404*/
405public static boolean checkIfHasRequestedLegacyExternalStorage(@NonNull Context context) {
406int targetSdkVersion = PackageUtils.getTargetSDKForPackage(context);
407
408if (targetSdkVersion >= Build.VERSION_CODES.R) {
409return Build.VERSION.SDK_INT == Build.VERSION_CODES.Q;
410} else if (targetSdkVersion == Build.VERSION_CODES.Q) {
411return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
412} else {
413return false;
414}
415}
416
417/**
418* Call to {@link Environment#isExternalStorageLegacy()} will not return the actual value defined
419* in app manifest for {@link android.R.attr#requestLegacyExternalStorage} attribute,
420* since an app may inherit its legacy state based on when it was first installed, target sdk and
421* other factors. To provide consistent experience for all users regardless of current legacy
422* state on a specific device, we directly use the value defined in app` manifest.
423*/
424public static boolean hasRequestedLegacyExternalStorage(@NonNull Context context,
425boolean showErrorMessage) {
426String errmsg;
427Boolean hasRequestedLegacyExternalStorage = PackageUtils.hasRequestedLegacyExternalStorage(context);
428if (hasRequestedLegacyExternalStorage != null && !hasRequestedLegacyExternalStorage) {
429errmsg = context.getString(R.string.error_has_not_requested_legacy_external_storage,
430context.getPackageName(), PackageUtils.getTargetSDKForPackage(context), Build.VERSION.SDK_INT);
431Logger.logError(LOG_TAG, errmsg);
432if (showErrorMessage)
433Logger.showToast(context, errmsg, true);
434return false;
435}
436
437return true;
438}
439
440
441
442
443
444/**
445* Check if {@link Manifest.permission#SYSTEM_ALERT_WINDOW} permission has been granted.
446*
447* @param context The context for operations.
448* @return Returns {@code true} if permission is granted, otherwise {@code false}.
449*/
450public static boolean checkDisplayOverOtherAppsPermission(@NonNull Context context) {
451if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M)
452return Settings.canDrawOverlays(context);
453else
454return true;
455}
456
457/** Wrapper for {@link #requestDisplayOverOtherAppsPermission(Context, int)}. */
458public static Error requestDisplayOverOtherAppsPermission(@NonNull Context context) {
459return requestDisplayOverOtherAppsPermission(context, -1);
460}
461
462/**
463* Request user to grant {@link Manifest.permission#SYSTEM_ALERT_WINDOW} permission to the app.
464*
465* @param context The context for operations, like an {@link Activity} or {@link Service} context.
466* It must be an instance of {@link Activity} or {@link AppCompatActivity} if
467* result is required via the Activity#onActivityResult() callback and
468* {@code requestCode} is `>=0`.
469* @param requestCode The request code to use while asking for permission. It must be `>=0` if
470* result it required.
471* @return Returns the {@code error} if requesting the permission was not successful, otherwise {@code null}.
472*/
473public static Error requestDisplayOverOtherAppsPermission(@NonNull Context context, int requestCode) {
474Logger.logInfo(LOG_TAG, "Requesting display over apps permission");
475
476if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
477return null;
478
479Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
480intent.setData(Uri.parse("package:" + context.getPackageName()));
481
482// Flag must not be passed for activity contexts, otherwise onActivityResult() will not be called with permission grant result.
483// Flag must be passed for non-activity contexts like services, otherwise "Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag" exception will be raised.
484if (!(context instanceof Activity))
485intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
486
487if (requestCode >=0)
488return ActivityUtils.startActivityForResult(context, requestCode, intent);
489else
490return ActivityUtils.startActivity(context, intent);
491}
492
493/**
494* Check if running on sdk 29 (android 10) or higher and {@link Manifest.permission#SYSTEM_ALERT_WINDOW}
495* permission has been granted or not.
496*
497* @param context The context for operations.
498* @param logResults If it should be logged that permission has been granted or not.
499* @return Returns {@code true} if permission is granted, otherwise {@code false}.
500*/
501public static boolean validateDisplayOverOtherAppsPermissionForPostAndroid10(@NonNull Context context,
502boolean logResults) {
503if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return true;
504
505if (!checkDisplayOverOtherAppsPermission(context)) {
506if (logResults)
507Logger.logWarn(LOG_TAG, context.getPackageName() + " does not have Display over other apps (SYSTEM_ALERT_WINDOW) permission");
508return false;
509} else {
510if (logResults)
511Logger.logDebug(LOG_TAG, context.getPackageName() + " already has Display over other apps (SYSTEM_ALERT_WINDOW) permission");
512return true;
513}
514}
515
516
517
518
519
520/**
521* Check if {@link Manifest.permission#REQUEST_IGNORE_BATTERY_OPTIMIZATIONS} permission has been
522* granted.
523*
524* @param context The context for operations.
525* @return Returns {@code true} if permission is granted, otherwise {@code false}.
526*/
527public static boolean checkIfBatteryOptimizationsDisabled(@NonNull Context context) {
528if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
529PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
530return powerManager.isIgnoringBatteryOptimizations(context.getPackageName());
531} else
532return true;
533}
534
535/** Wrapper for {@link #requestDisableBatteryOptimizations(Context, int)}. */
536public static Error requestDisableBatteryOptimizations(@NonNull Context context) {
537return requestDisableBatteryOptimizations(context, -1);
538}
539
540/**
541* Request user to grant {@link Manifest.permission#REQUEST_IGNORE_BATTERY_OPTIMIZATIONS}
542* permission to the app.
543*
544* @param context The context for operations, like an {@link Activity} or {@link Service} context.
545* It must be an instance of {@link Activity} or {@link AppCompatActivity} if
546* result is required via the Activity#onActivityResult() callback and
547* {@code requestCode} is `>=0`.
548* @param requestCode The request code to use while asking for permission. It must be `>=0` if
549* result it required.
550* @return Returns the {@code error} if requesting the permission was not successful, otherwise {@code null}.
551*/
552@SuppressLint("BatteryLife")
553public static Error requestDisableBatteryOptimizations(@NonNull Context context, int requestCode) {
554Logger.logInfo(LOG_TAG, "Requesting to disable battery optimizations");
555
556if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
557return null;
558
559Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
560intent.setData(Uri.parse("package:" + context.getPackageName()));
561
562// Flag must not be passed for activity contexts, otherwise onActivityResult() will not be called with permission grant result.
563// Flag must be passed for non-activity contexts like services, otherwise "Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag" exception will be raised.
564if (!(context instanceof Activity))
565intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
566
567if (requestCode >=0)
568return ActivityUtils.startActivityForResult(context, requestCode, intent);
569else
570return ActivityUtils.startActivity(context, intent);
571}
572
573}
574