Skip to content

Commit c6ab613

Browse files
authored
Improved permissions activity (#96)
* Improved permissions activity * Fixes * Refactors * Strings * Small refactor * Recycler view * Small refactor * Fix
1 parent a90cbda commit c6ab613

23 files changed

Lines changed: 662 additions & 224 deletions

‎client/app/src/main/AndroidManifest.xml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@
3232
</intent-filter>
3333
</activity>
3434

35+
<activity
36+
android:name=".GrantPermissionsActivity"
37+
android:label="@string/permissions_title"
38+
android:theme="@style/Theme.LocationHistory" />
39+
3540
<service
3641
android:name=".push.UnifiedPushService"
3742
android:enabled="true"
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.jackpf.locationhistory.client;
2+
3+
import android.Manifest;
4+
import android.content.Context;
5+
import android.os.Build;
6+
7+
import com.jackpf.locationhistory.client.permissions.AppRequirement;
8+
import com.jackpf.locationhistory.client.permissions.BackgroundSetting;
9+
import com.jackpf.locationhistory.client.permissions.RequiredPermission;
10+
11+
import java.util.ArrayList;
12+
import java.util.List;
13+
14+
public class AppRequirements {
15+
public static List<AppRequirement> getRequirements(Context context) {
16+
List<AppRequirement> requirements = new ArrayList<>();
17+
18+
requirements.add(new RequiredPermission(
19+
Manifest.permission.ACCESS_FINE_LOCATION,
20+
context.getString(R.string.permission_desc_fine_location),
21+
context.getString(R.string.permission_exp_fine_location)
22+
));
23+
24+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
25+
requirements.add(new RequiredPermission(
26+
Manifest.permission.ACCESS_BACKGROUND_LOCATION,
27+
context.getString(R.string.permission_desc_background_location),
28+
context.getString(R.string.permission_exp_background_location)
29+
));
30+
}
31+
32+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
33+
requirements.add(new RequiredPermission(
34+
Manifest.permission.POST_NOTIFICATIONS,
35+
context.getString(R.string.permission_desc_post_notifications),
36+
context.getString(R.string.permission_exp_post_notifications)
37+
));
38+
}
39+
40+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
41+
requirements.add(new BackgroundSetting(
42+
context.getString(R.string.permission_desc_ignore_optimisations),
43+
context.getString(R.string.permission_exp_ignore_optimisations)
44+
));
45+
}
46+
47+
return requirements;
48+
}
49+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package com.jackpf.locationhistory.client;
2+
3+
import android.annotation.SuppressLint;
4+
import android.content.Context;
5+
import android.os.Bundle;
6+
import android.view.LayoutInflater;
7+
import android.view.View;
8+
import android.view.ViewGroup;
9+
import android.widget.Button;
10+
import android.widget.TextView;
11+
12+
import androidx.annotation.NonNull;
13+
import androidx.appcompat.app.AppCompatActivity;
14+
import androidx.recyclerview.widget.DiffUtil;
15+
import androidx.recyclerview.widget.LinearLayoutManager;
16+
import androidx.recyclerview.widget.ListAdapter;
17+
import androidx.recyclerview.widget.RecyclerView;
18+
19+
import com.jackpf.locationhistory.client.permissions.AppRequirement;
20+
import com.jackpf.locationhistory.client.permissions.AppRequirementsUtil;
21+
22+
import java.util.ArrayList;
23+
import java.util.List;
24+
import java.util.Objects;
25+
26+
import lombok.Value;
27+
28+
public class GrantPermissionsActivity extends AppCompatActivity {
29+
private PermissionsAdapter adapter;
30+
private final List<AppRequirement> registeredAppRequirements = new ArrayList<>();
31+
32+
/**
33+
* Wrapper class to hold AppRequirement and its granted state for diffing
34+
*/
35+
@Value
36+
private static class PermissionItem {
37+
AppRequirement requirement;
38+
boolean isGranted;
39+
}
40+
41+
@Override
42+
protected void onCreate(Bundle savedInstanceState) {
43+
super.onCreate(savedInstanceState);
44+
setContentView(R.layout.activity_permissions);
45+
46+
adapter = new PermissionsAdapter(this);
47+
RecyclerView recyclerView = findViewById(R.id.recyclerView);
48+
recyclerView.setLayoutManager(new LinearLayoutManager(this));
49+
recyclerView.setAdapter(adapter);
50+
51+
setupPermissionsList();
52+
updateUI();
53+
}
54+
55+
private void setupPermissionsList() {
56+
List<AppRequirement> requirements = AppRequirements.getRequirements(this);
57+
for (AppRequirement requirement : requirements) {
58+
requirement.register(this, this::updateUI);
59+
registeredAppRequirements.add(requirement);
60+
}
61+
}
62+
63+
/**
64+
* Updates the RecyclerView with current permission status
65+
*/
66+
private void updateUI() {
67+
if (AppRequirementsUtil.allGranted(this, registeredAppRequirements)) {
68+
finish();
69+
return;
70+
}
71+
72+
List<PermissionItem> items = new ArrayList<>();
73+
for (AppRequirement requirement : registeredAppRequirements) {
74+
items.add(new PermissionItem(requirement, requirement.isGranted(this)));
75+
}
76+
adapter.submitList(items);
77+
}
78+
79+
private static class PermissionsAdapter extends ListAdapter<PermissionItem, PermissionsAdapter.ViewHolder> {
80+
private final Context context;
81+
82+
private static final DiffUtil.ItemCallback<PermissionItem> diffCallback =
83+
new DiffUtil.ItemCallback<PermissionItem>() {
84+
@Override
85+
public boolean areItemsTheSame(@NonNull PermissionItem oldItem, @NonNull PermissionItem newItem) {
86+
return Objects.equals(oldItem.requirement.getName(), newItem.requirement.getName());
87+
}
88+
89+
@Override
90+
// Lombok generates our equals method, linter doesn't see it
91+
@SuppressLint("DiffUtilEquals")
92+
public boolean areContentsTheSame(@NonNull PermissionItem oldItem, @NonNull PermissionItem newItem) {
93+
return oldItem.equals(newItem);
94+
}
95+
};
96+
97+
PermissionsAdapter(Context context) {
98+
super(diffCallback);
99+
this.context = context;
100+
}
101+
102+
@NonNull
103+
@Override
104+
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
105+
View view = LayoutInflater.from(parent.getContext())
106+
.inflate(R.layout.activity_permissions_item, parent, false);
107+
return new ViewHolder(view);
108+
}
109+
110+
@Override
111+
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
112+
PermissionItem item = getItem(position);
113+
holder.bind(item, context);
114+
}
115+
116+
static class ViewHolder extends RecyclerView.ViewHolder {
117+
private final TextView descriptionText;
118+
private final TextView subDescriptionText;
119+
private final Button grantButton;
120+
private final View grantedLabel;
121+
122+
ViewHolder(@NonNull View itemView) {
123+
super(itemView);
124+
descriptionText = itemView.findViewById(R.id.descriptionLabel);
125+
subDescriptionText = itemView.findViewById(R.id.subDescriptionLabel);
126+
grantButton = itemView.findViewById(R.id.grantButton);
127+
grantedLabel = itemView.findViewById(R.id.grantedLabel);
128+
}
129+
130+
void bind(PermissionItem item, Context context) {
131+
descriptionText.setText(item.requirement.getDescription());
132+
subDescriptionText.setText(item.requirement.getExplanation());
133+
134+
if (item.isGranted) {
135+
grantButton.setVisibility(View.GONE);
136+
grantedLabel.setVisibility(View.VISIBLE);
137+
} else {
138+
grantButton.setVisibility(View.VISIBLE);
139+
grantedLabel.setVisibility(View.GONE);
140+
grantButton.setOnClickListener(v -> item.requirement.request(context));
141+
}
142+
}
143+
}
144+
}
145+
}
Lines changed: 5 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,27 @@
11
package com.jackpf.locationhistory.client;
22

3-
import android.Manifest;
4-
import android.annotation.SuppressLint;
5-
import android.os.Build;
63
import android.os.Bundle;
7-
import android.provider.Settings;
84

9-
import androidx.annotation.NonNull;
105
import androidx.appcompat.app.AppCompatActivity;
116

127
import com.google.android.material.color.DynamicColors;
13-
import com.google.common.util.concurrent.Futures;
14-
import com.google.common.util.concurrent.ListenableFuture;
15-
import com.jackpf.locationhistory.PingResponse;
16-
import com.jackpf.locationhistory.client.client.ssl.TrustedCertStorage;
17-
import com.jackpf.locationhistory.client.client.util.GrpcFutureWrapper;
18-
import com.jackpf.locationhistory.client.config.ConfigRepository;
19-
import com.jackpf.locationhistory.client.grpc.BeaconClient;
20-
import com.jackpf.locationhistory.client.permissions.PermissionsFlow;
21-
import com.jackpf.locationhistory.client.permissions.PermissionsManager;
22-
import com.jackpf.locationhistory.client.ui.Toasts;
23-
import com.jackpf.locationhistory.client.util.Logger;
24-
25-
import java.io.IOException;
268

279
public class MainActivity extends AppCompatActivity {
28-
private ConfigRepository configRepo;
29-
private BeaconClient beaconClient;
30-
31-
private final Logger log = new Logger(this);
32-
33-
private PermissionsFlow permissionsFlow;
34-
35-
private PermissionsFlow createPermissionsFlow() {
36-
PermissionsManager permissionsManager = new PermissionsManager(this);
37-
PermissionsFlow permissionsFlow = new PermissionsFlow(this);
38-
39-
permissionsFlow.require(new String[]{Manifest.permission.ACCESS_FINE_LOCATION});
40-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
41-
permissionsFlow.thenRequire(new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION});
42-
}
43-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
44-
permissionsFlow.thenRequire(new String[]{Manifest.permission.POST_NOTIFICATIONS});
45-
}
46-
if (!permissionsManager.hasBackgroundPermission()) {
47-
permissionsFlow.requireSetting(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
48-
}
49-
50-
return permissionsFlow.onComplete(() -> {
51-
log.d("Permission flow complete");
52-
53-
log.d("Starting beacon worker...");
54-
BeaconWorkerFactory.createWorker(this);
55-
log.d("Beacon worker started");
56-
});
57-
}
58-
59-
public void refreshBeaconClient() {
60-
log.d("Refreshing beacon client");
61-
try {
62-
BeaconClientFactory.BeaconClientParams params = new BeaconClientFactory.BeaconClientParams(
63-
configRepo.getServerHost(),
64-
configRepo.getServerPort(),
65-
false,
66-
BeaconClientFactory.DEFAULT_TIMEOUT
67-
);
68-
beaconClient = BeaconClientFactory.createPooledClient(params, new TrustedCertStorage(this));
69-
} catch (IOException e) {
70-
beaconClient = null;
71-
}
72-
}
73-
74-
@SuppressLint("SetTextI18n")
7510
@Override
7611
protected void onCreate(Bundle bundle) {
7712
DynamicColors.applyToActivitiesIfAvailable(getApplication());
7813
super.onCreate(bundle);
7914

8015
setContentView(R.layout.activity_main);
81-
82-
permissionsFlow = createPermissionsFlow();
83-
permissionsFlow.start();
8416
}
8517

86-
8718
@Override
88-
protected void onStart() {
89-
super.onStart();
90-
91-
configRepo = new ConfigRepository(this);
92-
refreshBeaconClient();
93-
}
19+
protected void onResume() {
20+
super.onResume();
9421

95-
private BeaconClient getBeaconClient() throws IOException {
96-
if (beaconClient != null && !beaconClient.isClosed()) return beaconClient;
97-
else throw new IOException("Client not connected");
22+
// Start our worker!
23+
// Multiple calls are fine since we use ExistingPeriodicWorkPolicy.KEEP
24+
BeaconWorkerFactory.createWorker(this);
9825
}
9926

100-
public ListenableFuture<PingResponse> ping(GrpcFutureWrapper<PingResponse> callback) {
101-
try {
102-
return getBeaconClient().ping(callback);
103-
} catch (IOException e) {
104-
callback.onFailure(e);
105-
return Futures.immediateFailedFuture(e);
106-
}
107-
}
108-
109-
@Override
110-
public void onRequestPermissionsResult(int code, @NonNull String[] permissions, @NonNull int[] grantResult) {
111-
super.onRequestPermissionsResult(code, permissions, grantResult);
112-
permissionsFlow.onRequestPermissionsResult(code, permissions, grantResult, deniedPermission -> {
113-
log.w("Permissions %s was denied", deniedPermission);
114-
Toasts.show(this, R.string.toast_permission_denied);
115-
return false;
116-
});
117-
}
11827
}

‎client/app/src/main/java/com/jackpf/locationhistory/client/config/ConfigRepository.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public void setPrivateKey(String privateKey) {
7171
}
7272

7373
public String getServerHost() {
74-
return prefs.getString(SERVER_HOST_KEY, "10.0.2.2");
74+
return prefs.getString(SERVER_HOST_KEY, "");
7575
}
7676

7777
public void setServerHost(String host) {

‎client/app/src/main/java/com/jackpf/locationhistory/client/location/LocationProvider.java‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ public static class LocationData {
4040
String source;
4141
}
4242

43-
4443
public LocationProvider(Context context, PermissionsManager permissionsManager) {
4544
this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
4645
this.permissionsManager = permissionsManager;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.jackpf.locationhistory.client.permissions;
2+
3+
import android.content.Context;
4+
5+
import androidx.activity.result.ActivityResultCaller;
6+
7+
public interface AppRequirement {
8+
String getName();
9+
10+
String getDescription();
11+
12+
String getExplanation();
13+
14+
AppRequirement register(ActivityResultCaller caller, Runnable onResult);
15+
16+
boolean isGranted(Context context);
17+
18+
void request(Context context);
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.jackpf.locationhistory.client.permissions;
2+
3+
import android.content.Context;
4+
5+
import java.util.List;
6+
7+
public class AppRequirementsUtil {
8+
private AppRequirementsUtil() {
9+
}
10+
11+
public static boolean allGranted(Context context, List<AppRequirement> requirements) {
12+
for (AppRequirement requirement : requirements) {
13+
if (!requirement.isGranted(context)) {
14+
return false;
15+
}
16+
}
17+
return true;
18+
}
19+
}

0 commit comments

Comments
 (0)