我想在我的 Android 应用程序上实现 Dropbox oAuth2 流方法,以便用户可以获得刷新访问令牌,而无需每 4 小时更新我的代码我已经向我的相机应用程序添加了一个密钥,因此当用户单击它时,它会打开 oAuth 流操作但是当我按下它时我遇到了问题,然后它在谷歌中打开我设计的网址,然后我在保管箱的网站中按允许,我在我的网址中获得了授权代码,然后我直接返回到应用程序,但是当我按照片拍摄徽标拍照应用程序不断崩溃 这是我的应用程序代码
这是我的应用程序代码 `公共类 MainActivity 扩展 AppCompatActivity 实现 View.OnClickListener {
private static final int CAMERA_PERMISSION_REQUEST = 100;
private static final String[] REQUIRED_CAMERA_PERMISSIONS = new String[]{Manifest.permission.CAMERA};
private static final String DROPBOX_APP_KEY = "hc2p6uj44p9pyoc";
private static final String DROPBOX_APP_SECRET = "this is my secret code i cannot showed it";
private static final String DROPBOX_REDIRECT_URI = "https://decamera://auth-finish";
private String DROPBOX_ACCESS_TOKEN = null;
private PreviewView previewView;
private ImageCapture imageCapture;
private TextView addressTextView;
private ExecutorService cameraExecutor = Executors.newSingleThreadExecutor();
private FusedLocationProviderClient fusedLocationProviderClient;
private Geocoder geocoder;
// Add latitude and longitude variables
private double latitudeValue;
private double longitudeValue;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
previewView = findViewById(R.id.previewView);
ImageButton captureButton = findViewById(R.id.captureButton);
ImageButton leftCornerButton = findViewById(R.id.leftCornerButton);
addressTextView = findViewById(R.id.addressTextView);
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
geocoder = new Geocoder(this, Locale.getDefault());
captureButton.setOnClickListener(this);
leftCornerButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
initiateDropboxAuthorization();
}
});
addressTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showEditAddressDialog();
}
});
if (allPermissionsGranted()) {
startCamera();
getLastLocation();
} else {
ActivityCompat.requestPermissions(this, REQUIRED_CAMERA_PERMISSIONS, CAMERA_PERMISSION_REQUEST);
}
// Initialize Dropbox access token if available
SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE);
DROPBOX_ACCESS_TOKEN = prefs.getString("dropboxAccessToken", null);
// Check if the app was opened with a Dropbox authorization callback
handleDropboxAuthorizationCallback(getIntent());
}
// Override onNewIntent to handle the Dropbox authorization callback
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
handleDropboxAuthorizationCallback(intent);
}
private void initiateDropboxAuthorization() {
// Construct the Dropbox authorization URL
String authorizationUrl = "https://www.dropbox.com/oauth2/authorize" +
"?client_id=" + DROPBOX_APP_KEY +
"&response_type=code" +
"&token_access_type=offline" +
"&state=myState" + // Replace with your own state
"&redirect_uri=" + DROPBOX_REDIRECT_URI;
// Open the authorization URL in a web browser or WebView
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(authorizationUrl));
startActivity(browserIntent);
}
private String performPostRequest(String requestUrl, String urlParameters) {
try {
URL url = new URL(requestUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setRequestProperty("Content-Length", Integer.toString(urlParameters.getBytes().length));
connection.setDoOutput(true);
// Log the request details
Log.d("Dropbox", "POST Request URL: " + requestUrl);
Log.d("Dropbox", "POST Request Body: " + urlParameters);
// Write the request body
try (DataOutputStream wr = new DataOutputStream(connection.getOutputStream())) {
wr.writeBytes(urlParameters);
wr.flush();
}
// Get the response
try (InputStream is = connection.getInputStream();
BufferedReader rd = new BufferedReader(new InputStreamReader(is))) {
StringBuilder response = new StringBuilder();
String line;
while ((line = rd.readLine()) != null) {
response.append(line);
response.append('\r');
}
// Log the response
Log.d("Dropbox", "POST Response: " + response.toString());
return response.toString();
}
} catch (Exception e) {
// Log any exceptions
Log.e("Dropbox", "Error in HTTP request: " + e.getMessage());
return null;
}
}
private void handleDropboxAuthorizationCallback(Intent intent) {
Uri data = intent.getData();
if (data != null && data.toString().startsWith(DROPBOX_REDIRECT_URI)) {
// Authorization successful, extract the authorization code
String code = data.getQueryParameter("code");
// Log the authorization code for debugging
Log.d("Dropbox", "Authorization Code: " + code);
// Now, exchange the authorization code for an access token and refresh token
exchangeAuthorizationCodeForTokens(code);
}
}
private void exchangeAuthorizationCodeForTokens(String code) {
try {
String url = "https://api.dropbox.com/oauth2/token";
String requestBody = "code=" + code +
"&grant_type=authorization_code" +
"&client_id=" + DROPBOX_APP_KEY +
"&client_secret=" + DROPBOX_APP_SECRET +
"&redirect_uri=" + DROPBOX_REDIRECT_URI;
// Log the request details
Log.d("Dropbox", "Token Exchange Request URL: " + url);
Log.d("Dropbox", "Token Exchange Request Body: " + requestBody);
// Perform the POST request and obtain the JSON response
String jsonResponse = performPostRequest(url, requestBody);
// Log the response
Log.d("Dropbox", "Token Exchange Response: " + jsonResponse);
// Parse the JSON response to extract access and refresh tokens
JSONObject jsonObject = new JSONObject(jsonResponse);
DROPBOX_ACCESS_TOKEN = jsonObject.getString("access_token");
// Log the obtained access token
Log.d("Dropbox", "Access Token Obtained: " + DROPBOX_ACCESS_TOKEN);
// Save the Dropbox access token
SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE);
prefs.edit().putString("dropboxAccessToken", DROPBOX_ACCESS_TOKEN).apply();
} catch (Exception e) {
// Log any exceptions
Log.e("Dropbox", "Error in token exchange: " + e.getMessage());
}
}
private void showEditAddressDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Edit Address");
// Set up the input
View viewInflated = LayoutInflater.from(this).inflate(R.layout.edit_address_dialog, null);
final EditText input = viewInflated.findViewById(R.id.editTextAddress);
input.setText(addressTextView.getText()); // Pre-fill with existing address
builder.setView(viewInflated);
// Set up buttons
builder.setPositiveButton("Confirm", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Update the addressTextView with the edited address
String editedAddress = input.getText().toString();
addressTextView.setText(editedAddress);
}
});
builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel(); // Cancel the update
}
});
// Make the dialog cancelable by tapping outside
builder.setCancelable(true);
// Show the dialog
builder.show();
}
private boolean allPermissionsGranted() {
for (String permission : REQUIRED_CAMERA_PERMISSIONS) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
private void startCamera() {
ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this);
cameraProviderFuture.addListener(() -> {
try {
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
bindPreview(cameraProvider);
} catch (Exception e) {
e.printStackTrace();
}
}, ContextCompat.getMainExecutor(this));
}
private void bindPreview(ProcessCameraProvider cameraProvider) {
Preview preview = new Preview.Builder().build();
CameraSelector cameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build();
Camera camera = cameraProvider.bindToLifecycle((LifecycleOwner) this, cameraSelector, preview);
preview.setSurfaceProvider(previewView.getSurfaceProvider());
imageCapture = new ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.setTargetRotation(previewView.getDisplay().getRotation())
.build();
cameraProvider.unbindAll();
cameraProvider.bindToLifecycle((LifecycleOwner) this, cameraSelector, preview, imageCapture);
}
private void combineImageWithAddressAndChoices(File photoFile, String addressText, String firstQuestionChoice, String secondQuestionChoice, double latitude, double longitude) {
Bitmap photoBitmap = BitmapFactory.decodeFile(photoFile.getAbsolutePath());
int rotation = getRotationAngle(photoFile.getAbsolutePath());
if (rotation != 0) {
photoBitmap = rotateBitmap(photoBitmap, rotation);
}
Bitmap combinedBitmap = photoBitmap.copy(photoBitmap.getConfig(), true);
Canvas canvas = new Canvas(combinedBitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
float x = 130;
float yAddress = combinedBitmap.getHeight() - 800 + 130;
float yTimestamp = yAddress + paint.getTextSize() + 130; // Add more space between lines
float yFirstChoice = yTimestamp + paint.getTextSize() + 130; // Add more space between lines
float ySecondChoice = yFirstChoice + paint.getTextSize() + 130; // Add more space between lines
float yCoordinates = ySecondChoice + paint.getTextSize() + 130; // Add more space between lines
// Increase text size
float textSize = 130; // Adjust the text size as needed
paint.setTextSize(textSize);
// Add address with background
drawTextWithBackground(canvas, paint, "Address: " + addressText, x, yAddress);
// Add timestamp with background
String timeStamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date());
drawTextWithBackground(canvas, paint, "Timestamp: " + timeStamp, x, yTimestamp);
// Add user's choices with background
drawTextWithBackground(canvas, paint, "Betons: " + firstQuestionChoice, x, yFirstChoice);
drawTextWithBackground(canvas, paint, "Holzes: " + secondQuestionChoice, x, ySecondChoice);
// Add coordinates with background
String coordinatesText = String.format(Locale.getDefault(), "Latitude: %.6f, Longitude: %.6f", latitude, longitude);
drawTextWithBackground(canvas, paint, coordinatesText, x, yCoordinates);
saveCombinedImage(combinedBitmap, photoFile);
}
// Helper method to draw text with background
private void drawTextWithBackground(Canvas canvas, Paint paint, String text, float x, float y) {
// Get text bounds to determine the background width and height
Rect bounds = new Rect();
paint.getTextBounds(text, 0, text.length(), bounds);
// Add a semi-transparent background for the text
paint.setColor(Color.parseColor("#80000000")); // Adjust the color and transparency as needed
RectF backgroundRect = new RectF(x, y - bounds.height() + 20, x + bounds.width() + 40, y + 20);
canvas.drawRect(backgroundRect, paint);
paint.setColor(Color.WHITE); // Set text color back to white
// Draw the text
canvas.drawText(text, x + 20, y, paint);
}
private void getLastLocation() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
fusedLocationProviderClient.getLastLocation()
.addOnSuccessListener(this, new OnSuccessListener<Location>() {
@Override
public void onSuccess(Location location) {
if (location != null) {
latitudeValue = location.getLatitude();
longitudeValue = location.getLongitude();
updateAddress(location);
}
}
});
} else {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, CAMERA_PERMISSION_REQUEST);
}
}
private void updateAddress(Location location) {
try {
List<Address> addresses = geocoder.getFromLocation(location.getLatitude(), location.getLongitude(), 1);
if (addresses != null && addresses.size() > 0) {
Address address = addresses.get(0);
String fullAddress = address.getAddressLine(0);
addressTextView.setText(fullAddress);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void captureImage() {
// Create an AlertDialog to ask the user the first question
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Was ist die Art des Betons?");
// Add options to the dialog
String[] options = {"steif", "plastisch", "fließfähig"};
builder.setItems(options, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Handle the user's choice
String firstQuestionChoice = options[which];
// Now, ask the user the second question
askSecondQuestion(firstQuestionChoice);
}
});
// Show the dialog
builder.show();
}
private void askSecondQuestion(String firstQuestionChoice) {
// Create an AlertDialog to ask the user the second question
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Was ist die Art des holz?");
// Add options to the dialog
String[] options = {"Fichtenholz", "Eichenholz", "Buchenholz"};
builder.setItems(options, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Handle the user's choice for the second question
String secondQuestionChoice = options[which];
// Now, capture the image with both choices
captureImageAfterUserChoices(firstQuestionChoice, secondQuestionChoice);
}
});
// Show the dialog
builder.show();
}
private void captureImageAfterUserChoices(String firstQuestionChoice, String secondQuestionChoice) {
// Now, capture the image as before
File photoFile = createImageFile();
if (photoFile != null) {
ImageCapture.OutputFileOptions outputFileOptions =
new ImageCapture.OutputFileOptions.Builder(photoFile).build();
imageCapture.takePicture(outputFileOptions, ContextCompat.getMainExecutor(this), new ImageCapture.OnImageSavedCallback() {
@Override
public void onImageSaved(@Nullable ImageCapture.OutputFileResults outputFileResults) {
// Combine the image with the edited address, timestamp, latitude, and longitude before uploading to Dropbox
combineImageWithAddressAndChoices(photoFile, addressTextView.getText().toString(), firstQuestionChoice, secondQuestionChoice, latitudeValue, longitudeValue);
// Show the drawn photo to the user before uploading
showPreviewDialog(photoFile);
}
@Override
public void onError(@NonNull ImageCaptureException exception) {
runOnUiThread(() -> Toast.makeText(MainActivity.this, "Error capturing image: " + exception.getMessage(), Toast.LENGTH_SHORT).show());
}
});
} else {
runOnUiThread(() -> Toast.makeText(MainActivity.this, "Error creating photo file", Toast.LENGTH_SHORT).show());
}
}
// Helper method to show a preview dialog to the user
private void showPreviewDialog(File photoFile) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("To DropBox Server");
// Inflate the layout containing an ImageView
View viewInflated = LayoutInflater.from(this).inflate(R.layout.preview_dialog, null);
ImageView imageViewPreview = viewInflated.findViewById(R.id.imageViewPreview);
imageViewPreview.setImageBitmap(BitmapFactory.decodeFile(photoFile.getAbsolutePath()));
builder.setView(viewInflated);
// Set up buttons
builder.setPositiveButton("Confirm", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Call the AsyncTask to upload the combined photo to Dropbox in the background
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
uploadToDropbox(photoFile);
return null;
}
}.execute();
}
});
builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Delete the temporary photo file
if (photoFile.exists()) {
photoFile.delete();
}
}
});
// Make the dialog cancelable by tapping outside
builder.setCancelable(true);
// Show the dialog
builder.show();
}
private File createImageFile() {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
try {
if (storageDir != null) {
File tempFile = File.createTempFile(imageFileName, ".jpg", storageDir);
System.out.println("File path: " + tempFile.getAbsolutePath());
return tempFile;
} else {
runOnUiThread(() -> Toast.makeText(MainActivity.this, "Error creating storage directory", Toast.LENGTH_SHORT).show());
}
} catch (IOException e) {
e.printStackTrace();
runOnUiThread(() -> Toast.makeText(MainActivity.this, "Error creating photo file: " + e.getMessage(), Toast.LENGTH_SHORT).show());
}
return null;
}
private int getRotationAngle(String imagePath) {
ExifInterface exifInterface;
try {
exifInterface = new ExifInterface(imagePath);
} catch (IOException e) {
e.printStackTrace();
return 0;
}
int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
return 90;
case ExifInterface.ORIENTATION_ROTATE_180:
return 180;
case ExifInterface.ORIENTATION_ROTATE_270:
return 270;
default:
return 0;
}
}
private Bitmap rotateBitmap(Bitmap bitmap, int angle) {
Matrix matrix = new Matrix();
matrix.postRotate(angle);
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
private void saveCombinedImage(Bitmap combinedBitmap, File photoFile) {
try {
FileOutputStream out = new FileOutputStream(photoFile);
combinedBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
runOnUiThread(() -> Toast.makeText(MainActivity.this, "Error combining image with address: " + e.getMessage(), Toast.LENGTH_SHORT).show());
}
}
private void uploadToDropbox(File photoFile) {
if (photoFile == null || !photoFile.exists()) {
runOnUiThread(() -> Toast.makeText(MainActivity.this, "Error: Photo file does not exist", Toast.LENGTH_SHORT).show());
return;
}
if (DROPBOX_ACCESS_TOKEN == null) {
runOnUiThread(() -> Toast.makeText(MainActivity.this, "Error: Dropbox access token is null", Toast.LENGTH_SHORT).show());
return;
}
DbxRequestConfig config = DbxRequestConfig.newBuilder("Decamera").build();
DbxClientV2 client = new DbxClientV2(config, DROPBOX_ACCESS_TOKEN);
try {
String remotePath = "/Decamera/" + photoFile.getName();
try (InputStream in = new FileInputStream(photoFile)) {
client.files().uploadBuilder(remotePath)
.withMode(WriteMode.ADD)
.uploadAndFinish(in);
runOnUiThread(() -> Toast.makeText(MainActivity.this, "Photo uploaded to Dropbox", Toast.LENGTH_SHORT).show());
}
} catch (Exception e) {
e.printStackTrace();
runOnUiThread(() -> Toast.makeText(MainActivity.this, "Error uploading to Dropbox: " + e.getMessage(), Toast.LENGTH_SHORT).show());
}
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.captureButton) {
captureImage();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == CAMERA_PERMISSION_REQUEST) {
if (allPermissionsGranted()) {
startCamera();
getLastLocation();
} else {
runOnUiThread(() -> Toast.makeText(this, "Camera permissions not granted.", Toast.LENGTH_SHORT).show());
finish();
}
}
}
}`
这是我的日志猫 致命异常:主要 进程:com.example.decamera,PID:10620 java.lang.NullPointerException:尝试调用虚拟方法 'void androidx.camera.core.ImageCapture.takePicture(androidx.camera.core.ImageCapture$OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture$OnImageSavedCallback )' 在空对象引用上 在com.example.decamera.MainActivity.captureImageAfterUserChoices(MainActivity.java:472) 在 com.example.decamera.MainActivity.access$700(MainActivity.java:77) 在 com.example.decamera.MainActivity$7.onClick(MainActivity.java:456) 在 androidx.appcompat.app.AlertController$AlertParams$3.onItemClick(AlertController.java:1068) 在 android.widget.AdapterView.performItemClick(AdapterView.java:362) 在 android.widget.AbsListView.performItemClick(AbsListView.java:1689) 在 android.widget.AbsListView$PerformClick.run(AbsListView.java:4130) 在 android.widget.AbsListView$7.run(AbsListView.java:6612) 在 android.os.Handler.handleCallback(Handler.java:873) 在 android.os.Handler.dispatchMessage(Handler.java:99) 在 android.os.Looper.loop(Looper.java:214) 在 android.app.ActivityThread.main(ActivityThread.java:7078) 在 java.lang.reflect.Method.invoke(本机方法) 在 com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494) 在 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:964)