• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 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 com.example.android.vdmdemo.host;
18 
19 import android.Manifest;
20 import android.app.AlertDialog;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.ServiceConnection;
25 import android.content.pm.PackageManager;
26 import android.os.Bundle;
27 import android.os.IBinder;
28 import android.util.Log;
29 import android.view.Menu;
30 import android.view.MenuInflater;
31 import android.view.MenuItem;
32 import android.view.View;
33 import android.widget.Button;
34 import android.widget.GridView;
35 
36 import androidx.activity.result.ActivityResultLauncher;
37 import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
38 import androidx.appcompat.app.AppCompatActivity;
39 import androidx.appcompat.widget.Toolbar;
40 import androidx.core.content.ContextCompat;
41 
42 import com.example.android.vdmdemo.common.EdgeToEdgeUtils;
43 
44 import dagger.hilt.android.AndroidEntryPoint;
45 
46 import java.util.function.Consumer;
47 
48 import javax.inject.Inject;
49 
50 /**
51  * VDM Host activity, streaming apps to a remote device and processing the input coming from there.
52  */
53 @AndroidEntryPoint(AppCompatActivity.class)
54 public class MainActivity extends Hilt_MainActivity {
55     public static final String TAG = "VdmHost";
56 
57     private VdmService mVdmService = null;
58     private GridView mLauncher = null;
59     private Button mHomeDisplayButton = null;
60     private Button mMirrorDisplayButton = null;
61     private LauncherAdapter mLauncherAdapter = null;
62     private final Consumer<Boolean> mVirtualDeviceListener = this::updateLauncherVisibility;
63 
64     private final ActivityResultLauncher<String> mRequestPermissionLauncher =
65             registerForActivityResult(new RequestPermission(), isGranted -> {
66                 // no-op
67             });
68 
69     private final ActivityResultLauncher<String> mVdmServicePermissionLauncher =
70             registerForActivityResult(new RequestPermission(), isGranted -> {
71                 if (mVdmService == null) {
72                     attemptStartVdmService();
73                 }
74             });
75 
76     private final ServiceConnection mServiceConnection =
77             new ServiceConnection() {
78 
79                 @Override
80                 public void onServiceConnected(ComponentName className, IBinder binder) {
81                     Log.i(TAG, "Connected to VDM Service");
82                     mVdmService = ((VdmService.LocalBinder) binder).getService();
83                     mVdmService.addVirtualDeviceListener(mVirtualDeviceListener);
84                     updateLauncherVisibility(mVdmService.isVirtualDeviceActive());
85                 }
86 
87                 @Override
88                 public void onServiceDisconnected(ComponentName className) {
89                     Log.i(TAG, "Disconnected from VDM Service");
90                     mVdmService = null;
91                 }
92             };
93 
94     @Inject
95     PreferenceController mPreferenceController;
96 
97     @Override
onCreate(Bundle savedInstanceState)98     public void onCreate(Bundle savedInstanceState) {
99         super.onCreate(savedInstanceState);
100 
101         setContentView(R.layout.activity_main);
102         Toolbar toolbar = requireViewById(R.id.main_tool_bar);
103         setSupportActionBar(toolbar);
104 
105         EdgeToEdgeUtils.applyTopInsets(toolbar);
106 
107         mHomeDisplayButton = requireViewById(R.id.create_home_display);
108         mHomeDisplayButton.setVisibility(View.GONE);
109         mMirrorDisplayButton = requireViewById(R.id.create_mirror_display);
110         mMirrorDisplayButton.setVisibility(View.GONE);
111 
112         mLauncherAdapter = new LauncherAdapter(this, mPreferenceController);
113         mLauncher = requireViewById(R.id.app_grid);
114         mLauncher.setVisibility(View.GONE);
115         mLauncher.setAdapter(mLauncherAdapter);
116         mLauncher.setOnItemClickListener(
117                 (parent, v, position, id) -> {
118                     Intent intent = mLauncherAdapter.createPendingRemoteIntent(position);
119                     if (intent == null || mVdmService == null) {
120                         return;
121                     }
122                     mVdmService.startStreaming(intent);
123                 });
124         mLauncher.setOnItemLongClickListener(
125                 (parent, v, position, id) -> {
126                     Intent intent = mLauncherAdapter.createPendingRemoteIntent(position);
127                     if (intent == null || mVdmService == null) {
128                         return true;
129                     }
130                     int[] remoteDisplayIds = mVdmService.getRemoteDisplayIds();
131                     if (remoteDisplayIds.length == 0) {
132                         mVdmService.startStreaming(intent);
133                     } else {
134                         String[] displays = new String[remoteDisplayIds.length + 1];
135                         for (int i = 0; i < remoteDisplayIds.length; ++i) {
136                             displays[i] = "Display " + remoteDisplayIds[i];
137                         }
138                         displays[remoteDisplayIds.length] = "New display";
139                         AlertDialog.Builder alertDialogBuilder =
140                                 new AlertDialog.Builder(MainActivity.this);
141                         alertDialogBuilder.setTitle("Choose display");
142                         alertDialogBuilder.setItems(
143                                 displays,
144                                 (dialog, which) -> {
145                                     if (which == remoteDisplayIds.length) {
146                                         mVdmService.startStreaming(intent);
147                                     } else {
148                                         mVdmService.startIntentOnDisplayIndex(intent, which);
149                                     }
150                                 });
151                         alertDialogBuilder.show();
152                     }
153                     return true;
154                 });
155     }
156 
157     @Override
onStart()158     protected void onStart() {
159         super.onStart();
160         attemptStartVdmService();
161     }
162 
163     @Override
onResume()164     protected void onResume() {
165         super.onResume();
166         if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
167                 == PackageManager.PERMISSION_DENIED) {
168             mRequestPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO);
169         }
170     }
171 
172     @Override
onStop()173     protected void onStop() {
174         super.onStop();
175         if (mVdmService != null) {
176             mVdmService.removeVirtualDeviceListener(mVirtualDeviceListener);
177         }
178         unbindService(mServiceConnection);
179     }
180 
attemptStartVdmService()181     private void attemptStartVdmService() {
182         // Need NEARBY_WIFI_DEVICES for WifiAware
183         if (ContextCompat.checkSelfPermission(this, Manifest.permission.NEARBY_WIFI_DEVICES)
184                 == PackageManager.PERMISSION_DENIED) {
185             mVdmServicePermissionLauncher.launch(Manifest.permission.NEARBY_WIFI_DEVICES);
186             return;
187         }
188 
189         // Need POST_NOTIFICATIONS for the foreground Service Notification
190         if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
191                 == PackageManager.PERMISSION_DENIED) {
192             mVdmServicePermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS);
193             return;
194         }
195         Log.i(TAG, "Starting Vdm Host service");
196         Intent intent = new Intent(this, VdmService.class);
197         startForegroundService(intent);
198         bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
199     }
200 
updateLauncherVisibility(boolean virtualDeviceActive)201     private void updateLauncherVisibility(boolean virtualDeviceActive) {
202         final int visibility = virtualDeviceActive ? View.VISIBLE : View.GONE;
203         runOnUiThread(
204                 () -> {
205                     if (mLauncher != null) {
206                         mLauncherAdapter.update();
207                         if (mPreferenceController.getBoolean(
208                                 R.string.internal_pref_home_displays_supported)) {
209                             mLauncher.setVisibility(visibility);
210                         } else {
211                             mLauncher.setVisibility(View.GONE);
212                         }
213                     }
214                     if (mHomeDisplayButton != null) {
215                         mHomeDisplayButton.setEnabled(
216                                 mPreferenceController.getBoolean(
217                                         R.string.internal_pref_home_displays_supported));
218                         mHomeDisplayButton.setVisibility(visibility);
219                     }
220                     if (mMirrorDisplayButton != null) {
221                         mMirrorDisplayButton.setEnabled(
222                                 VdmCompat.isMirrorDisplaySupported(this, mPreferenceController));
223                         mMirrorDisplayButton.setVisibility(visibility);
224                     }
225                 });
226     }
227 
228     /** Process a home display request. */
onCreateHomeDisplay(View view)229     public void onCreateHomeDisplay(View view) {
230         mVdmService.startStreamingHome();
231     }
232 
233     /** Process a mirror display request. */
onCreateMirrorDisplay(View view)234     public void onCreateMirrorDisplay(View view) {
235         mVdmService.startMirroring();
236     }
237 
238     @Override
onCreateOptionsMenu(Menu menu)239     public boolean onCreateOptionsMenu(Menu menu) {
240         MenuInflater inflater = getMenuInflater();
241         inflater.inflate(R.menu.options, menu);
242         return true;
243     }
244 
245     @Override
onOptionsItemSelected(MenuItem item)246     public boolean onOptionsItemSelected(MenuItem item) {
247         switch (item.getItemId()) {
248             case R.id.settings:
249                 startActivity(new Intent(this, SettingsActivity.class));
250                 return true;
251             case R.id.input:
252                 startActivity(new Intent(this, InputActivity.class));
253                 return true;
254             default:
255                 return super.onOptionsItemSelected(item);
256         }
257     }
258 }
259