Skip to content

Commit b12a1f7

Browse files
committed
Open Street Map Notes Upload clases and add unit test
1 parent 3759f1f commit b12a1f7

5 files changed

Lines changed: 382 additions & 274 deletions

File tree

app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@
7070
android:label="@string/osm_upload"
7171
android:exported="true">
7272
</activity>
73+
<activity android:name=".activity.OpenStreetMapNotesUpload"
74+
android:theme="@style/AppTheme"/>
7375
<activity
7476
android:name="net.openid.appauth.RedirectUriReceiverActivity"
7577
tools:node="replace" android:exported="true">
@@ -114,7 +116,6 @@
114116
</intent-filter>
115117
</activity>
116118
<activity android:name=".activity.AvailableLayouts" />
117-
<activity android:name=".activity.OpenStreetMapNotesUpload"/>
118119

119120
<service
120121
android:name=".service.gps.GPSLogger"
Lines changed: 117 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,44 @@
11
package net.osmtracker.activity;
22

3-
import android.app.Activity;
43
import android.content.Intent;
54
import android.content.SharedPreferences;
6-
import android.content.pm.PackageInfo;
7-
import android.content.pm.PackageManager;
85
import android.net.Uri;
96
import android.os.Bundle;
10-
import android.preference.PreferenceManager;
117
import android.util.Log;
12-
import android.view.LayoutInflater;
13-
import android.view.View;
14-
import android.view.View.OnClickListener;
158
import android.widget.Button;
169
import android.widget.TextView;
10+
import android.widget.Toast;
11+
12+
import androidx.activity.result.ActivityResultLauncher;
13+
import androidx.activity.result.contract.ActivityResultContracts;
14+
import androidx.appcompat.app.AppCompatActivity;
15+
import androidx.preference.PreferenceManager;
1716

1817
import net.openid.appauth.AuthorizationException;
1918
import net.openid.appauth.AuthorizationRequest;
2019
import net.openid.appauth.AuthorizationResponse;
2120
import net.openid.appauth.AuthorizationService;
2221
import net.openid.appauth.AuthorizationServiceConfiguration;
2322
import net.openid.appauth.ResponseTypeValues;
24-
import net.openid.appauth.TokenResponse;
23+
2524
import net.osmtracker.OSMTracker;
2625
import net.osmtracker.R;
2726
import net.osmtracker.osm.OpenStreetMapConstants;
2827
import net.osmtracker.osm.UploadToOpenStreetMapNotesTask;
2928

29+
import java.util.concurrent.ExecutorService;
30+
import java.util.concurrent.Executors;
31+
3032
/**
31-
* <p>Uploads a note on OSM using the API and
32-
* OAuth authentication.</p>
33+
* <p>Uploads a note on OSM using the API and OAuth authentication.</p>
3334
*
3435
* <p>This activity may be called twice during a single
3536
* upload cycle: First to start the upload, then a second
3637
* time when the user has authenticated using the browser.</p>
3738
*
3839
* @author Most of the code was made by Nicolas Guillaumin, adapted by Jose Andrés Vargas Serrano
3940
*/
40-
public class OpenStreetMapNotesUpload extends Activity {
41+
public class OpenStreetMapNotesUpload extends AppCompatActivity {
4142

4243
private static final String TAG = OpenStreetMapNotesUpload.class.getSimpleName();
4344

@@ -51,19 +52,39 @@ public class OpenStreetMapNotesUpload extends Activity {
5152

5253
/** URL that the browser will call once the user is authenticated */
5354
public final static String OAUTH2_CALLBACK_URL = "osmtracker://osm-upload/oath2-completed/";
54-
public final static int RC_AUTH = 7;
55-
5655
private AuthorizationService authService;
56+
private ActivityResultLauncher<Intent> authLauncher;
5757

5858
@Override
5959
protected void onCreate(Bundle savedInstanceState) {
60-
super.onCreate(savedInstanceState);
61-
View uploadNoteView = getLayoutInflater().inflate(R.layout.osm_note_upload, null);
62-
setContentView(uploadNoteView);
63-
setTitle(R.string.osm_note_upload);
64-
65-
noteContentView = uploadNoteView.findViewById(R.id.wplist_item_name);
66-
noteFooterView = uploadNoteView.findViewById(R.id.osm_note_footer);
60+
super.onCreate(savedInstanceState);
61+
62+
// Register the launcher
63+
authLauncher = registerForActivityResult(
64+
new ActivityResultContracts.StartActivityForResult(),
65+
result -> {
66+
// This replaces the logic previously in onActivityResult
67+
Intent data = result.getData();
68+
// RC_AUTH logic
69+
if (data != null) {
70+
AuthorizationResponse resp = AuthorizationResponse.fromIntent(data);
71+
AuthorizationException ex = AuthorizationException.fromIntent(data);
72+
73+
if (resp != null) {
74+
exchangeAuthorizationCode(resp);
75+
} else {
76+
Log.e(TAG, "Authorization failed: " + (ex != null ? ex.getMessage() : "Unknown error"));
77+
Toast.makeText(this, R.string.osm_upload_oauth_failed, Toast.LENGTH_SHORT).show();
78+
}
79+
}
80+
}
81+
);
82+
83+
84+
setContentView(R.layout.osm_note_upload);
85+
setTitle(R.string.osm_note_upload);
86+
noteContentView = findViewById(R.id.wplist_item_name);
87+
noteFooterView = findViewById(R.id.osm_note_footer);
6788

6889
// Read and cache extras
6990
Bundle extras = getIntent().getExtras();
@@ -85,20 +106,10 @@ protected void onCreate(Bundle savedInstanceState) {
85106
noteContentView.setText(initialNoteText);
86107
noteFooterView.setText(getString(R.string.osm_note_footer, appName, version));
87108

88-
final Button btnOk = (Button) findViewById(R.id.osm_note_upload_button_ok);
89-
btnOk.setOnClickListener(new OnClickListener() {
90-
@Override
91-
public void onClick(View v) {
92-
startUpload(noteId);
93-
}
94-
});
95-
final Button btnCancel = (Button) findViewById(R.id.osm_note_upload_button_cancel);
96-
btnCancel.setOnClickListener(new OnClickListener() {
97-
@Override
98-
public void onClick(View v) {
99-
finish();
100-
}
101-
});
109+
final Button btnOk = findViewById(R.id.osm_note_upload_button_ok);
110+
btnOk.setOnClickListener(v -> startUpload(noteId));
111+
final Button btnCancel = findViewById(R.id.osm_note_upload_button_cancel);
112+
btnCancel.setOnClickListener(v -> finish());
102113

103114
}
104115

@@ -109,100 +120,96 @@ public void onClick(View v) {
109120
*/
110121
private void startUpload(long noteId) {
111122
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
112-
if ( prefs.contains(OSMTracker.Preferences.KEY_OSM_OAUTH2_ACCESSTOKEN) ) {
113-
// Re-use saved token
114-
uploadToOsm(prefs.getString(OSMTracker.Preferences.KEY_OSM_OAUTH2_ACCESSTOKEN, ""), noteId);
115-
} else {
116-
// Open browser and request token
123+
String accessToken = prefs.getString(OSMTracker.Preferences.KEY_OSM_OAUTH2_ACCESSTOKEN, null);
124+
125+
if (accessToken != null && !accessToken.isEmpty()) {
126+
// STATE: AUTHORIZED. Re-use saved token
127+
Log.d(TAG, "Token found, proceeding to upload note to OSM.");
128+
uploadToOsm(accessToken, noteId);
129+
} else {
130+
// STATE: UNAUTHORIZED. Open browser and request token
131+
Log.d(TAG, "No token found, requesting authorization.");
117132
requestOsmAuth();
118133
}
119134
}
120135
/*
121-
* Init Authorization request workflow.
136+
* Init Authorization request workflow. Launches browser to request authorization.
122137
*/
123138
public void requestOsmAuth() {
124139
// Authorization service configuration
125-
AuthorizationServiceConfiguration serviceConfig =
126-
new AuthorizationServiceConfiguration(
140+
AuthorizationServiceConfiguration serviceConfig = new AuthorizationServiceConfiguration(
127141
Uri.parse(OpenStreetMapConstants.OAuth2.Urls.AUTHORIZATION_ENDPOINT),
128142
Uri.parse(OpenStreetMapConstants.OAuth2.Urls.TOKEN_ENDPOINT));
129143

130-
// Obtaining an authorization code
131-
Uri redirectURI = Uri.parse(OAUTH2_CALLBACK_URL);
132-
AuthorizationRequest.Builder authRequestBuilder =
133-
new AuthorizationRequest.Builder(
134-
serviceConfig, OpenStreetMapConstants.OAuth2.CLIENT_ID,
135-
ResponseTypeValues.CODE, redirectURI);
136-
AuthorizationRequest authRequest = authRequestBuilder
137-
.setScope(OpenStreetMapConstants.OAuth2.SCOPE)
138-
.build();
139-
140-
// Start activity.
144+
// Obtaining an authorization code
145+
AuthorizationRequest authRequest = new AuthorizationRequest.Builder(
146+
serviceConfig,
147+
OpenStreetMapConstants.OAuth2.CLIENT_ID,
148+
ResponseTypeValues.CODE,
149+
Uri.parse(OAUTH2_CALLBACK_URL))
150+
.setScope(OpenStreetMapConstants.OAuth2.SCOPE)
151+
.build();
152+
153+
// Start activity.
141154
authService = new AuthorizationService(this);
142155
Intent authIntent = authService.getAuthorizationRequestIntent(authRequest);
143-
startActivityForResult(authIntent, RC_AUTH); //when done onActivityResult will be called.
156+
//when done onActivityResult will be called.
157+
// Use the launcher instead of startActivityForResult
158+
authLauncher.launch(authIntent);
144159
}
145160

146-
147-
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
148-
super.onActivityResult(requestCode, resultCode, data);
149-
// User is returning from authentication
150-
if (requestCode == RC_AUTH) {
151-
// Handling the authorization response
152-
AuthorizationResponse resp = AuthorizationResponse.fromIntent(data);
153-
AuthorizationException ex = AuthorizationException.fromIntent(data);
154-
// ... process the response or exception ...
155-
if (ex != null) {
156-
Log.e(TAG, "Authorization Error. Exception received from server.");
157-
Log.e(TAG, ex.getMessage());
158-
} else if (resp == null) {
159-
Log.e(TAG, "Authorization Error. Null response from server.");
160-
} else {
161-
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
162-
163-
//Exchanging the authorization code
164-
authService.performTokenRequest(
165-
resp.createTokenExchangeRequest(),
166-
new AuthorizationService.TokenResponseCallback() {
167-
@Override public void onTokenRequestCompleted(
168-
TokenResponse resp, AuthorizationException ex) {
169-
if (resp != null) {
170-
// exchange succeeded
171-
SharedPreferences.Editor editor = prefs.edit();
172-
editor.putString(OSMTracker.Preferences.KEY_OSM_OAUTH2_ACCESSTOKEN, resp.accessToken);
173-
editor.apply();
174-
//continue with the note Upload.
175-
uploadToOsm(resp.accessToken, noteId);
176-
} else {
177-
// authorization failed, check ex for more details
178-
Log.e(TAG, "OAuth failed.");
179-
}
180-
}
181-
});
182-
}
183-
} else {
184-
Log.e(TAG, "Unexpected requestCode:" + requestCode + ".");
185-
}
186-
}
161+
private void exchangeAuthorizationCode(AuthorizationResponse resp) {
162+
authService.performTokenRequest(resp.createTokenExchangeRequest(), (tokenResp, tokenEx) -> {
163+
if (tokenResp != null && tokenResp.accessToken != null) {
164+
// STATE: TRANSITION TO AUTHORIZED
165+
persistToken(tokenResp.accessToken);
166+
uploadToOsm(tokenResp.accessToken, noteId);
167+
} else {
168+
Log.e(TAG, "Token exchange failed");
169+
}
170+
});
171+
}
172+
173+
private void persistToken(String token) {
174+
PreferenceManager.getDefaultSharedPreferences(this).edit()
175+
.putString(OSMTracker.Preferences.KEY_OSM_OAUTH2_ACCESSTOKEN, token)
176+
.apply();
177+
}
187178

188179
/**
189180
* Uploads notes to OSM.
190181
*/
191182
public void uploadToOsm(String accessToken, long noteId) {
192-
String noteText = noteContentView.getText().toString();
193-
String footer = noteFooterView.getText().toString();
194-
if (!footer.isEmpty()) {
195-
noteText = noteText + "\n\n" + footer;
196-
}
197-
new UploadToOpenStreetMapNotesTask(
198-
OpenStreetMapNotesUpload.this,
199-
accessToken,
200-
noteId,
201-
noteText,
202-
latitude,
203-
longitude
204-
).execute();
205-
}
206-
183+
String noteText = noteContentView.getText().toString();
184+
String footer = noteFooterView.getText().toString();
185+
if (!footer.isEmpty()) {
186+
noteText = noteText + "\n\n" + footer;
187+
}
188+
189+
// Final variables for the background thread
190+
final String finalNoteText = noteText;
191+
192+
// This replaces the deprecated AsyncTask.execute()
193+
ExecutorService executor = Executors.newSingleThreadExecutor();
194+
executor.execute(() -> {
195+
try {
196+
new UploadToOpenStreetMapNotesTask(
197+
OpenStreetMapNotesUpload.this,
198+
accessToken,
199+
noteId,
200+
finalNoteText,
201+
latitude,
202+
longitude
203+
).run();
204+
} catch (Exception e) {
205+
Log.e(TAG, "Error during OSM Note upload", e);
206+
runOnUiThread(() ->
207+
Toast.makeText(this, R.string.osm_upload_error, Toast.LENGTH_SHORT).show()
208+
);
209+
} finally {
210+
executor.shutdown();
211+
}
212+
});
213+
}
207214

208215
}

0 commit comments

Comments
 (0)