简体   繁体   中英

android app permissions is not asking properly

public class EEmployeeApplication extends Application {
public void onCreate() {
    super.onCreate();
    try {
        mContext = getApplicationContext();
        dbHandler = new DataBaseModule(this);

        dbHandler.getReadableDatabase();
        dbHandler.close();

        dbHandler = getDbHandler();

        curUser = dbHandler.getActiveUserInfo();

        eEMPSharedPreference = new eEmployeeSharedPreference();
        DataSyncStartAlarm newAlarm = new DataSyncStartAlarm();
        newAlarm.startDataSyncAlarm(mContext, null);

    } catch (Exception e) {
        Log.d("eEmp/Application ", e.toString());
    }
}
@Override
public void onTerminate() {
    try {
    } catch (Exception e) {
        Log.d("eEmp/Application ", e.toString());
    }
    super.onTerminate();
}

public static boolean isNetAvailable() {
    try {
        ConnectivityManager connectivity = (ConnectivityManager)
                mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
        {
            NetworkInfo netInfo = connectivity.getActiveNetworkInfo();
            if (netInfo != null) {
                NetworkInfo[] info = connectivity.getAllNetworkInfo();
                if ((info == null) || (!netInfo.isConnectedOrConnecting())) {
                    return false;
                } else {
                    return true;
                }
            }
        }
        return false;
    } catch (Exception e) {
        Log.d("eEmp/Application", e.toString());
        return false;
    }
}

public DataBaseModule getDbHandler() {
    return dbHandler;
}

public void checkUserAvailability(Activity activity) {

    try {
        if (curUser == null)  // No Employee registered in local DB
        {
            activity.finish();
            Intent loginScreen = new Intent(mContext, Login_Activity.class);
            loginScreen.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            loginScreen.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(loginScreen);
        } else {
            activity.finish();
            List<EmpZonesResponseDTO> empZonesResponseDTOS ;
            empZonesResponseDTOS = eEMPSharedPreference.getZones(mContext);
            if (empZonesResponseDTOS == null){
                Intent dashboardAct = new Intent(mContext, Login_Activity.class);
                dashboardAct.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                dashboardAct.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(dashboardAct);
            }else {
                Intent dashboardAct = new Intent(mContext, NavigationActivity.class);
                dashboardAct.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                dashboardAct.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(dashboardAct);
            }

        }
    } catch (Exception except) {
        Log.d("eEmp/LoginActivity", except.toString());
    }
}

public static Context getContext() {
    return mContext;
}

public void SyncMasterData(NavigationActivity NavActivity) {
    try{

        if (isNetAvailable()) {

            Log.d("eEmp/LoginCheck", "Internet is available");
            SyncMasterDataDownloadTask SyncTask = new SyncMasterDataDownloadTask();
            SyncTask.setContextandActivity(this,NavActivity);
            SyncTask.execute();
        } else   
        {
            Log.d("eEmp/SyncMasterData", "Internet is not available");

            NavActivity.DismissMasterDataDownload();
            Snackbar
                    .make(NavActivity.navigationView,
                            "No Internet connection, Unable to Sync Account", Snackbar.LENGTH_LONG)
                    .show();
        }

    }catch (Exception e){
        e.printStackTrace();
    }
}

public String getActivityFragmentTag(EmpConstants.WorkType aSelectedWorkType) {
    String selectedTag;
    switch (aSelectedWorkType) {
        case Dashboard:
            selectedTag = EmpConstants.DashBoard_Info_Tag;
            break;
        case Profile:
            selectedTag = EmpConstants.Personal_Info_Tag;
            break;
        case Entry:
            selectedTag = EmpConstants.Entry_Info_Tag;
            break;
        case Plan:
            selectedTag = EmpConstants.Plan_Info_Tag;
            break;
        case Report:
            selectedTag = EmpConstants.Report_Info_Tag;
            break;
        case QR:
            selectedTag = EmpConstants.QR_Info_Tag;
            break;
        case Miscellaneous:
            selectedTag = EmpConstants.Miscellaneous_Info_Tag;
            break;
        case Nothing:
            selectedTag = "";
            break;
        default:
            selectedTag = "";
            break;
    }
    return selectedTag;
}

// Location Method
public GpsLocationListener getGpsLocationListener() {
    try {
        if (gpsLocationListener == null) {
            gpsLocationListener = new GpsLocationListener();
            gpsLocationListener.getGPSCordinates();
            //Log.d("eEmp/getGPSLocationList","Location Listener Created");
        }
    } catch (Exception gpsLocationExp) {
        Log.d("eEmp/getGPSLocationList", "Error raised due to " + gpsLocationExp.toString());
    }
    return gpsLocationListener;
}


public String getEmpImageDirPath() {
    try {
        return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString()
                + EmpConstants.appDir;
    } catch (Exception e) {
        Log.d("eEmp/ImgDir", e.toString());
        return "";
    }

}

public String getEmpThumbImageDirPath() {
    try {
        return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString()
                + EmpConstants.thumbnailDir;
    } catch (Exception e) {
        Log.d("eEmp/ThumbImgDir", e.toString());
        return "";
    }
}
public Bitmap loadFromFile(String filename) {
    try {
        File f = new File(filename);
        if (!f.exists()) {
            return null;
        }

        final int width = EmpConstants.DisplayWidth;//mDisplay.widthPixels;
        final int height = EmpConstants.DisplayHeight;//mDisplay.heightPixels;
        Bitmap tmp = decodeSampledBitmapFromResource(filename, width, height);
        return tmp;
    } catch (Exception e) {
        return null;
    }
}
public static Bitmap decodeSampledBitmapFromResource(String imgFile,
                                                     int reqWidth, int reqHeight) {

    try {
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(imgFile, options);

        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);


        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFile(imgFile, options);
    } catch (Exception sampleBitmapConvExp) {
        Log.d("eEmp/sampleBMPConv", "Error raised due to " + sampleBitmapConvExp.toString());
        return null;
    }
}
public static int calculateInSampleSize(
        BitmapFactory.Options options, int reqWidth, int reqHeight) {
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }
    return inSampleSize;
}


public void checkImageDirectories() {
    try {
        File eEmpPhotosDir = new File(getEmpImageDirPath());
        if (!eEmpPhotosDir.exists()) {
            eEmpPhotosDir.mkdir();
        }

        File eEmpThumbPhotosDir = new File(getEmpThumbImageDirPath());

        if (!eEmpThumbPhotosDir.exists())
            eEmpThumbPhotosDir.mkdir();
    } catch (Exception e) {
        Log.d("eEmp/CheckDir", e.toString());
    }
}

public void showFullImage(Context context, String curImgFile) {
    try {

        if (curImgFile.length() != 0) {
            AlertDialog.Builder builder = new AlertDialog.Builder(context);

            final Dialog nagDialog = new Dialog(context, android.R.style.Theme_Translucent_NoTitleBar_Fullscreen);
            nagDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
            nagDialog.setCancelable(false);
            nagDialog.setContentView(R.layout.preview_image);
            Button btnClose = (Button) nagDialog.findViewById(R.id.btnIvClose);
            ImageView ivPreview = (ImageView) nagDialog.findViewById(R.id.iv_preview_image);
            TextView tvLoading = (TextView) nagDialog.findViewById(R.id.tvImgLoading);
            ivPreview.setTag(tvLoading);
            ImageRequest imgOrgReq = new ImageRequest();
            imgOrgReq.imgView = ivPreview;
            imgOrgReq.ImgType = EmpConstants.ImageLoadingType.OriginalImage;
            imgOrgReq.context = (EEmployeeApplication) mContext;
            BitmapWorkerTask statusImg = new BitmapWorkerTask(imgOrgReq);
            statusImg.execute(curImgFile);

            btnClose.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View arg0) {

                    nagDialog.dismiss();
                }
            });
            nagDialog.show();
        }
    } catch (Exception e) {
        Log.d("eEmp/ShowFullImg", e.getLocalizedMessage());
    }
}

// delete selected image from external storage
public void deleteSelImgFromStorage(String imgPath, Context context) {
    try {
        File imgFile = new File(imgPath);
        if (imgPath.contains(EmpConstants.appName)) {
            if (imgFile.exists()) {
                imgFile.delete();
            }
            MediaScannerConnection.scanFile(context, new String[]{imgPath}, null,
                    new MediaScannerConnection.OnScanCompletedListener() {
                        public void onScanCompleted(String path, Uri uri) {

                        }
                    });
        }
    } catch (Exception e) {
        Log.i("Delete image", e.toString());
    }
}
// For Marshmallow
// To Check permissions for Marshmallow
public List<String> askPermissions(ArrayList<EmpConstants.PermissionsList> ReqPermnsList) {
    List<String> permissionsNeeded = new ArrayList<String>();
    for (EmpConstants.PermissionsList perm : ReqPermnsList) {
        switch (perm) {
            case PhonePerm:
                if (addPermission(Manifest.permission.READ_PHONE_STATE))
                    permissionsNeeded.add(Manifest.permission.READ_PHONE_STATE);
                break;
            case External_StoragePerm:
                if (addPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE))
                    permissionsNeeded.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
                if (addPermission(Manifest.permission.READ_EXTERNAL_STORAGE))
                    permissionsNeeded.add(Manifest.permission.READ_EXTERNAL_STORAGE);
                break;
            case LocationPerm:
                if (addPermission(Manifest.permission.ACCESS_COARSE_LOCATION))
                    permissionsNeeded.add(Manifest.permission.ACCESS_COARSE_LOCATION);
                if (addPermission(Manifest.permission.ACCESS_FINE_LOCATION))
                    permissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION);
                break;
            case CameraPerm:
                if (addPermission(Manifest.permission.CAMERA))
                    permissionsNeeded.add(Manifest.permission.CAMERA);
                break;
        }
    }
    return permissionsNeeded;
}

public boolean addPermission(String permission) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
            return true;
        }
    }
    return false;
}
public boolean isAnyPermissionRequired(ArrayList<EmpConstants.PermissionsList> ReqPermnsList) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        try {
            if (ReqPermnsList.size() == 0) {
                return false;
            } else {

                for (EmpConstants.PermissionsList perm : ReqPermnsList) {
                    switch (perm) {
                        case PhonePerm:
                            if (checkSelfPermission(Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_DENIED) {
                                return true;
                            }
                            break;
                        case External_StoragePerm:
                            if ((checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED)
                                    || (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED)) {
                                return true;
                            }
                            break;
                        case LocationPerm:
                            if ((checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_DENIED)
                                    || (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_DENIED)) {
                                return true;
                            }
                            break;
                        case CameraPerm:
                            if ((checkSelfPermission(Manifest.permission.CAMERA)== PackageManager.PERMISSION_DENIED)){
                                return true;
                            }
                            break;
                        default:
                            return false;
                    }
                }
                return false;
            }
        } catch (Exception checkPermExpt) {
            Log.d("eEmp/CheckPerms", "Exception due to " + checkPermExpt.toString());
        }
    }
    return false;
}

public void getNetworkProvideLocation() {
    try {

        if (longitude_Value == 0 || latitude_Value == 0) {
            LocationManager locationManager = (LocationManager) EEmployeeApplication.getContext().getSystemService(Context.LOCATION_SERVICE);
            boolean isNetworkEnable = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
            if (isNetworkEnable) {
                if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                    return;
                }
                Location lastLocation = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
                if (lastLocation != null) {
                    longitude_Value = lastLocation.getLongitude();
                    latitude_Value = lastLocation.getLatitude();
                    Log.d("eEmp/getNwValues", "Network Coordinates Updated" );
                }
            } else {
                return;
            }
        }

    } catch (Exception networkLocExp) {
        Log.d("eEmp/getNwValues", "Exception due to " + networkLocExp.toString());
    }
public int isValidUser() {
    try {
        if (isNetAvailable()) {
            HTTPCommunication loginHTTPRequest;
            EEmployeeHTTPResponse result;
            if (curUser != null) {
                Log.d("eEmp/ValLogin", "Validating User in Background");
                loginHTTPRequest = new HTTPCommunication();
                EmployeeInfoDTO loginInfo = new EmployeeInfoDTO();
                loginHTTPRequest.setRequestType(EmpConstants.HTTPRequestType.NewUser);

                loginInfo.EMPID = curUser.getEMPID();
                loginInfo.Password = curUser.getPassword();
                result = loginHTTPRequest.SendHTTPRequest(loginInfo);

                if (result != null) {
                    if (result.HTTPStatusCode == 200) {
                        if (result.Data != null) {
                            return result.Data.ResponseCode;
                        }
                    }
                }
            }
        }
        return 0; // Invalid User
    } catch (Exception e) {
        Log.d("eEmp/ValLoginError", e.toString());
        return 0;
    }
}

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    MultiDex.install(this);
}
}

LaunchingActivity.java

public class LaunchingActivity extends Activity implements GoogleApiClient.ConnectionCallbacks,
    GoogleApiClient.OnConnectionFailedListener, OnTaskStateChange {
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    try {
        setContentView(R.layout.activity_launching);
        context = (EEmployeeApplication) EEmployeeApplication.getContext();

        if (EEmployeeApplication.isNetAvailable()){

            // Read IMEI Number
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

                ArrayList<EmpConstants.PermissionsList> reqPermissionsList = new ArrayList<>();
                if ((context.eEMPSharedPreference != null) && (context.eEMPSharedPreference.getIMEI().isEmpty())) {
                    reqPermissionsList.add(EmpConstants.PermissionsList.PhonePerm);
                }
                reqPermissionsList.add(EmpConstants.PermissionsList.External_StoragePerm);
                reqPermissionsList.add(EmpConstants.PermissionsList.LocationPerm);
                reqPermissionsList.add(EmpConstants.PermissionsList.CameraPerm);

                // Check All Permissions
                if (context.isAnyPermissionRequired(reqPermissionsList)) {
                    requestPermission(context.askPermissions(reqPermissionsList));
                } else {
                    readIMEINumber();
                }

            } else {
                readIMEINumber();
            }


            int x = BuildConfig.VERSION_CODE;
            AppVersionTask mTask = new AppVersionTask();
            mTask.setListener(this);


            APPVERREQ aRequest = new APPVERREQ();
            aRequest.requestType = EmpConstants.HTTPRequestType.App_Ver;

            APP_VERSION loginInfo = new APP_VERSION();
            loginInfo.APP_VER = BuildConfig.VERSION_NAME;
            aRequest.RequestObj = loginInfo;
            mTask.execute(aRequest);
        }

        try {
            Log.d("eEmp/Test", "MasterdataTaskStarted");
            MasterDataLoadTask masterDataLoadTask = new MasterDataLoadTask();
            masterDataLoadTask.setContextandActivity(context, this);
            masterDataLoadTask.execute();


        } catch (Exception MasterDataExcept) {
            Log.d("eEmp/MstrDataTask", "Exception Occurred due to " + MasterDataExcept.toString());
            context.checkUserAvailability(this);
        }


        Log.d("eEmp/LaunchCreate", "Launching Created");
    } catch (Exception e) {
        Log.d("eEmp/LaunchActvty", e.toString());
    }
}

@Override
protected void onStart() {
    super.onStart();
    if ((mGoogleApiClient != null) && (!mGoogleApiClient.isConnected())) {
        mGoogleApiClient.connect();
    }
    Log.d("eEMP/LaunchOnStart", "Launch OnStart");

}

@Override
public boolean onCreateOptionsMenu(Menu menu) {

    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    return super.onOptionsItemSelected(item);
}

// For Marshmallow
// To Check permissions for Marshmallow
public void requestPermission(List<String> permissionsNeeded) {
    if (permissionsNeeded.size() > 0) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            requestPermissions(permissionsNeeded.toArray(new String[permissionsNeeded.size()]),
                    EmpConstants.REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
        }
    }
}


@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    readIMEINumber();
}


// The callback for the management of the user settings regarding location
private ResultCallback<LocationSettingsResult> mResultCallbackFromSettings = new ResultCallback<LocationSettingsResult>() {
    @Override
    public void onResult(LocationSettingsResult result) {
        final Status status = result.getStatus();

        switch (status.getStatusCode()) {
            case LocationSettingsStatusCodes.SUCCESS:

                // GPS is already in On State
                initiateLocationRequest();

                break;
            case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
                try {

                    status.startResolutionForResult(
                            LaunchingActivity.this,
                            REQUEST_CHECK_SETTINGS);
                } catch (IntentSender.SendIntentException e) {
                    // Ignore the error.
                }
                break;

            default:
                context.checkUserAvailability(LaunchingActivity.this);

                Log.e("eEmp/Location", "Settings change unavailable. We have no way to fix the settings so we won't show the dialog.");
                break;
        }
    }
};


@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    try {

        switch (requestCode) {
            case REQUEST_CHECK_SETTINGS:
                switch (resultCode) {
                    case Activity.RESULT_OK:

                        if (mGoogleApiClient.isConnected()) {
                            initiateLocationRequest();
                        }
                        break;
                    case Activity.RESULT_CANCELED:

                        context.checkUserAvailability(this);
                        break;
                    default:
                        break;
                }
                break;
        }
    } catch (Exception expt) {
        Log.d("eEmp/AcvtResult", "Exception occurred due to " + expt.toString());
    }
}
public void readIMEINumber() {
    try {
        if ((context.eEMPSharedPreference != null) && (context.eEMPSharedPreference.getIMEI().isEmpty())) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

                if (checkSelfPermission(Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
                    TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
                    context.eEMPSharedPreference.setIMEI(telephonyManager.getDeviceId());
                }
            } else {
                TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
                context.eEMPSharedPreference.setIMEI(telephonyManager.getDeviceId());
            }
}
if (!checkGPSStatus()) {
            LocationRequest mLocationRequest = LocationRequest.create();
            mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
            if (checkPlayServices()) {

                LocationSettingsRequest.Builder locationSettingsRequestBuilder = new LocationSettingsRequest.Builder()
                        .addLocationRequest(mLocationRequest);

                locationSettingsRequestBuilder.setAlwaysShow(true);

                PendingResult<LocationSettingsResult> result =
                        LocationServices.SettingsApi.checkLocationSettings(mGoogleApiClient, locationSettingsRequestBuilder.build());
                result.setResultCallback(mResultCallbackFromSettings);

            } else {

                context.checkUserAvailability(this);
            }
        } else {

            initiateLocationRequest();
        }

    } catch (Exception IMEIExp) {
        Log.d("eEmp/IMEIGet", "Unable to get IMEI number due to " + IMEIExp.toString());
    }
}
private boolean checkGPSStatus() {
    try {
        locationManager = (LocationManager) EEmployeeApplication.getContext().getSystemService(Context.LOCATION_SERVICE);

        if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
            return true;
        } else {
            return false;
        }
    } catch (Exception chkGPSExpt) {
        Log.d("eEmp/chkGPS", " Exception Raised due to  " + chkGPSExpt.toString());
        return false;
    }
}


@Override
protected void onDestroy() {
    Log.d("eEmp/Launching", "onDestroy Called");
    super.onDestroy();
}

@Override
public void onTaskStateChange(EmpConstants.TaskState State, String Msg, EEmployeeHTTPResponse result) {
    switch (State){
        case RequestStarted:
            break;
        case ResponseRecvd:
            break;
        case ResponseProcessing:
            break;
        case ResponseProcessed:
            if (result != null) {
                if (result.HTTPStatusCode == 200) {
                    if (result.Data != null) {
                            APP_VERSION_RESPONSE aResponse = (APP_VERSION_RESPONSE) result.Data.ActionResult;
                            if (aResponse!= null){
                                if (aResponse.APP_VER.equals(BuildConfig.VERSION_NAME)){
                                    try{
                                        System.out.print("Yes");
                                    }catch (Exception e){
                                        Log.d("/eEmp","/Excp due to"+e.toString());
                                    }

                                }
                            }
                    }
                }
            }
    }
}
}

In my android app, I am asking for permissions at launching of application. Actually What I want is after launching application, it should ask for permissions and take user input whether accepted or denied then only should move to login page.

But after launching, it is asked for permissions and it is not waiting for user input means permissions dialog is opening and after 5 to 10 seconds login page is opening even though user not responding to permissions dialog.

Why it is happening like this?

Any help would be appreciated.

清单文件中声明的权限

Your assumption that the android framework should take care of the permission handling process and should hold on to loading the UI components until the user has interacted with the permission is wrong. You need to handle to permission interaction with the user yourself. Here's how your flow should be :

  1. Ask the user for permission.
  2. Wait for the user to interact with the dialog box.
  3. Read the interaction response and evaluate if the permission was granted.
  4. If the permission was granted, load the UI components.

In other words, you need to make sure that you hold on to doing anything that requires the permissions until the permissions are actually there.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM