1 /*
2  * Copyright 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.camera.integration.core;
18 
19 import static androidx.camera.integration.core.CameraXService.ACTION_BIND_USE_CASES;
20 import static androidx.camera.integration.core.CameraXService.EXTRA_IMAGE_ANALYSIS_ENABLED;
21 import static androidx.camera.integration.core.CameraXService.EXTRA_IMAGE_CAPTURE_ENABLED;
22 import static androidx.camera.integration.core.CameraXService.EXTRA_VIDEO_CAPTURE_ENABLED;
23 
24 import android.Manifest;
25 import android.content.Intent;
26 import android.content.pm.PackageManager;
27 import android.os.Build;
28 import android.os.Bundle;
29 import android.widget.Toast;
30 import android.widget.ToggleButton;
31 
32 import androidx.activity.result.ActivityResultLauncher;
33 import androidx.activity.result.contract.ActivityResultContracts;
34 import androidx.appcompat.app.AppCompatActivity;
35 import androidx.core.content.ContextCompat;
36 
37 import org.jspecify.annotations.NonNull;
38 import org.jspecify.annotations.Nullable;
39 
40 import java.util.ArrayList;
41 import java.util.List;
42 import java.util.Objects;
43 
44 /**
45  * An activity to configure CameraXService.
46  *
47  * <p>It will:
48  * <ul>
49  *   <li>Grant runtime permissions.</li>
50  *   <li>Start the CameraXService automatically.</li>
51  *   <li>Control which use cases are bound to the CameraXService lifecycle.</li>
52  * </ul>
53  */
54 public class ServiceSettingActivity extends AppCompatActivity {
55 
56     private static final String[] REQUIRED_PERMISSIONS;
57 
58     static {
59         List<String> permissions = new ArrayList<>();
60         permissions.add(android.Manifest.permission.CAMERA);
61         permissions.add(android.Manifest.permission.RECORD_AUDIO);
62         // From Android T, skips the permission check of WRITE_EXTERNAL_STORAGE since it won't be
63         // granted any more.
64         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
65             permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
66         } else {
67             // From Android T, POST_NOTIFICATIONS is required for foreground service to post
68             // notification.
69             permissions.add(Manifest.permission.POST_NOTIFICATIONS);
70         }
71         REQUIRED_PERMISSIONS = permissions.toArray(new String[0]);
72     }
73 
74     private ToggleButton mButtonVideo;
75     private ToggleButton mButtonImage;
76     private ToggleButton mButtonAnalysis;
77 
78     @Override
onCreate(@ullable Bundle savedInstanceState)79     protected void onCreate(@Nullable Bundle savedInstanceState) {
80         super.onCreate(savedInstanceState);
81 
82         setContentView(R.layout.activity_service_setting);
83         mButtonVideo = findViewById(R.id.video_toggle);
84         mButtonImage = findViewById(R.id.image_toggle);
85         mButtonAnalysis = findViewById(R.id.analysis_toggle);
86 
87         mButtonVideo.setOnClickListener(v -> bindUseCases());
88         mButtonImage.setOnClickListener(v -> bindUseCases());
89         mButtonAnalysis.setOnClickListener(v -> bindUseCases());
90 
91         grantPermissions();
92     }
93 
onPermissionGranted()94     private void onPermissionGranted() {
95         launchService();
96         bindUseCases();
97     }
98 
launchService()99     private void launchService() {
100         Intent intent = createServiceIntent(null);
101         ContextCompat.startForegroundService(this, intent);
102     }
103 
bindUseCases()104     private void bindUseCases() {
105         Intent intent = createServiceIntent(ACTION_BIND_USE_CASES);
106         intent.putExtra(EXTRA_VIDEO_CAPTURE_ENABLED, mButtonVideo.isChecked());
107         intent.putExtra(EXTRA_IMAGE_CAPTURE_ENABLED, mButtonImage.isChecked());
108         intent.putExtra(EXTRA_IMAGE_ANALYSIS_ENABLED, mButtonAnalysis.isChecked());
109         ContextCompat.startForegroundService(this, intent);
110     }
111 
createServiceIntent(@ullable String action)112     private @NonNull Intent createServiceIntent(@Nullable String action) {
113         Intent intent = new Intent(this, CameraXService.class);
114         intent.setAction(action);
115         return intent;
116     }
117 
isPermissionMissing()118     private boolean isPermissionMissing() {
119         for (String permission : REQUIRED_PERMISSIONS) {
120             if (ContextCompat.checkSelfPermission(this, permission)
121                     != PackageManager.PERMISSION_GRANTED) {
122                 return true;
123             }
124         }
125         return false;
126     }
127 
grantPermissions()128     private void grantPermissions() {
129         if (isPermissionMissing()) {
130             ActivityResultLauncher<String[]> permissionLauncher = registerForActivityResult(
131                     new ActivityResultContracts.RequestMultiplePermissions(),
132                     result -> {
133                         for (String permission : REQUIRED_PERMISSIONS) {
134                             if (!Objects.requireNonNull(result.get(permission))) {
135                                 Toast.makeText(this, "Camera permission denied.",
136                                         Toast.LENGTH_SHORT).show();
137                                 finish();
138                                 return;
139                             }
140                         }
141                         onPermissionGranted();
142                     });
143 
144             permissionLauncher.launch(REQUIRED_PERMISSIONS);
145         } else {
146             onPermissionGranted();
147         }
148     }
149 }
150