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.google.android.car.kitchensink.remoteaccess; 18 19 import static android.car.remoteaccess.CarRemoteAccessManager.TASK_TYPE_ENTER_GARAGE_MODE; 20 import static android.car.remoteaccess.CarRemoteAccessManager.TASK_TYPE_CUSTOM; 21 import static android.preference.PreferenceManager.getDefaultSharedPreferences; 22 23 import android.car.Car; 24 import android.car.remoteaccess.CarRemoteAccessManager; 25 import android.car.remoteaccess.CarRemoteAccessManager.InVehicleTaskScheduler; 26 import android.car.remoteaccess.CarRemoteAccessManager.ScheduleInfo; 27 import android.content.SharedPreferences; 28 import android.os.Bundle; 29 import android.util.Log; 30 import android.view.LayoutInflater; 31 import android.view.View; 32 import android.view.ViewGroup; 33 import android.widget.ArrayAdapter; 34 import android.widget.EditText; 35 import android.widget.Spinner; 36 import android.widget.TableLayout; 37 import android.widget.TableRow; 38 import android.widget.TableRow.LayoutParams; 39 import android.widget.TextView; 40 41 import androidx.fragment.app.Fragment; 42 43 import com.google.android.car.kitchensink.R; 44 45 import org.json.JSONArray; 46 import org.json.JSONException; 47 import org.json.JSONObject; 48 49 import java.time.Duration; 50 import java.util.ArrayList; 51 import java.util.List; 52 import java.util.concurrent.atomic.AtomicInteger; 53 54 public final class RemoteAccessTestFragment extends Fragment { 55 56 private static final String TAG = KitchenSinkRemoteTaskService.class.getSimpleName(); 57 58 private SharedPreferences mSharedPref; 59 60 private Car mCar; 61 private CarRemoteAccessManager mRemoteAccessManager; 62 private AtomicInteger mScheduleId = new AtomicInteger(0); 63 private Spinner mTaskType; 64 private EditText mRemoteTaskDataView; 65 private EditText mTaskDelayView; 66 private EditText mTaskRepeatView; 67 private EditText mTaskIntervalView; 68 69 @Override onCreate(Bundle savedInstanceState)70 public void onCreate(Bundle savedInstanceState) { 71 super.onCreate(savedInstanceState); 72 mSharedPref = getDefaultSharedPreferences(getContext()); 73 disconnectCar(); 74 mCar = Car.createCar(getContext(), /* handler= */ null, Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, 75 (Car car, boolean ready) -> { 76 if (ready) { 77 mRemoteAccessManager = (CarRemoteAccessManager) car.getCarManager( 78 Car.CAR_REMOTE_ACCESS_SERVICE); 79 } else { 80 mCar = null; 81 mRemoteAccessManager = null; 82 } 83 }); 84 } 85 86 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance)87 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) { 88 View v = inflater.inflate(R.layout.remote_access, container, 89 /* attachToRoot= */ false); 90 91 v.findViewById(R.id.refresh_remote_task_btn).setOnClickListener(this::refresh); 92 v.findViewById(R.id.clear_remote_task_btn).setOnClickListener(this::clear); 93 v.findViewById(R.id.schedule_task_btn).setOnClickListener(this::scheduleTask); 94 95 Spinner taskTypeSpinner = (Spinner) v.findViewById(R.id.remote_task_type_spinner); 96 ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(getContext(), 97 R.array.remote_task_types, android.R.layout.simple_spinner_item); 98 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 99 taskTypeSpinner.setAdapter(adapter); 100 101 mRemoteTaskDataView = (EditText) v.findViewById(R.id.remote_task_data); 102 mTaskDelayView = (EditText) v.findViewById(R.id.task_delay); 103 mTaskType = taskTypeSpinner; 104 mTaskRepeatView = (EditText) v.findViewById(R.id.task_repeat); 105 mTaskIntervalView = (EditText) v.findViewById(R.id.task_interval); 106 return v; 107 } 108 109 @Override onStart()110 public void onStart() { 111 super.onStart(); 112 refresh(getView()); 113 } 114 115 @Override onDestroy()116 public void onDestroy() { 117 super.onDestroy(); 118 disconnectCar(); 119 } 120 disconnectCar()121 private void disconnectCar() { 122 if (mCar != null && mCar.isConnected()) { 123 mRemoteAccessManager.clearRemoteTaskClient(); 124 mCar.disconnect(); 125 mCar = null; 126 } 127 } 128 129 private static class TaskInfo { 130 public String taskTime; 131 public String taskId; 132 public String taskData; 133 public int remainingTimeSec; 134 } 135 createNewTd(TableRow taskRow, String tag, String text)136 private void createNewTd(TableRow taskRow, String tag, String text) { 137 TextView tdView = new TextView(getContext()); 138 tdView.setLayoutParams(new TableRow.LayoutParams(0, LayoutParams.WRAP_CONTENT, 1)); 139 tdView.setTag(tag); 140 tdView.setText(text); 141 taskRow.addView(tdView); 142 } 143 showTaskInfoList(List<TaskInfo> tasks)144 private void showTaskInfoList(List<TaskInfo> tasks) { 145 TableLayout taskList = (TableLayout) getView().findViewById(R.id.remote_task_list); 146 // Remove all rows except the first one. 147 while (taskList.getChildCount() > 1) { 148 taskList.removeView(taskList.getChildAt(taskList.getChildCount() - 1)); 149 } 150 for (TaskInfo info : tasks) { 151 TableRow taskRow; 152 taskRow = new TableRow(getContext()); 153 int viewId = View.generateViewId(); 154 taskRow.setId(viewId); 155 taskRow.setLayoutParams(new TableRow.LayoutParams(LayoutParams.MATCH_PARENT, 156 LayoutParams.MATCH_PARENT)); 157 createNewTd(taskRow, "taskTime", info.taskTime); 158 createNewTd(taskRow, "taskId", info.taskId); 159 createNewTd(taskRow, "taskData", info.taskData); 160 createNewTd(taskRow, "remainingTimeSec", String.valueOf(info.remainingTimeSec)); 161 taskList.addView(taskRow); 162 } 163 } 164 refresh(View v)165 private void refresh(View v) { 166 String taskListJson = mSharedPref.getString(KitchenSinkRemoteTaskService.PREF_KEY, "{}"); 167 System.out.println(taskListJson); 168 List<TaskInfo> tasks = new ArrayList<>(); 169 try { 170 JSONObject jsonObject = new JSONObject(taskListJson); 171 JSONArray taskList = (JSONArray) jsonObject.get(KitchenSinkRemoteTaskService.PREF_KEY); 172 for (int i = 0; i < taskList.length(); i++) { 173 JSONObject task = (JSONObject) taskList.get(i); 174 TaskInfo taskInfo = new TaskInfo(); 175 taskInfo.taskTime = task.getString(KitchenSinkRemoteTaskService.TASK_TIME_KEY); 176 taskInfo.taskId = task.getString(KitchenSinkRemoteTaskService.TASK_ID_KEY); 177 taskInfo.taskData = task.getString( 178 KitchenSinkRemoteTaskService.TASK_DATA_KEY); 179 taskInfo.remainingTimeSec = task.getInt( 180 KitchenSinkRemoteTaskService.TASK_DURATION_KEY); 181 tasks.add(taskInfo); 182 } 183 } catch (JSONException e) { 184 Log.e(TAG, "failed to parse task JSON: " + taskListJson, e); 185 } 186 showTaskInfoList(tasks); 187 } 188 clear(View v)189 private void clear(View v) { 190 mSharedPref.edit().putString(KitchenSinkRemoteTaskService.PREF_KEY, "{}").apply(); 191 refresh(v); 192 } 193 scheduleTask(View v)194 private void scheduleTask(View v) { 195 Log.e(TAG, "scheduleTask"); 196 InVehicleTaskScheduler taskScheduler = mRemoteAccessManager.getInVehicleTaskScheduler(); 197 if (taskScheduler == null) { 198 Log.e(TAG, "Task scheduling is not supported"); 199 return; 200 } 201 int taskTypePos = mTaskType.getSelectedItemPosition(); 202 String taskData = mRemoteTaskDataView.getText().toString(); 203 if (taskTypePos != 0 && taskData.length() == 0) { 204 Log.e(TAG, "No task data specified"); 205 return; 206 } 207 int delay = Integer.parseInt(mTaskDelayView.getText().toString()); 208 long startTimeInEpochSeconds = (long) (System.currentTimeMillis() / 1000) + (long) delay; 209 String scheduleId = "scheduleId" + mScheduleId.getAndIncrement(); 210 ScheduleInfo.Builder scheduleInfoBuilder; 211 if (taskTypePos == 0) { 212 // Enter garage mode. 213 scheduleInfoBuilder = new ScheduleInfo.Builder( 214 scheduleId, TASK_TYPE_ENTER_GARAGE_MODE, startTimeInEpochSeconds); 215 } else { 216 if (taskTypePos == 1) { 217 taskData = "SetTemp:" + Float.parseFloat(taskData); 218 } 219 scheduleInfoBuilder = new ScheduleInfo.Builder( 220 scheduleId, TASK_TYPE_CUSTOM, startTimeInEpochSeconds) 221 .setTaskData(taskData.getBytes()); 222 } 223 224 int count = Integer.parseInt(mTaskRepeatView.getText().toString()); 225 scheduleInfoBuilder.setCount(count); 226 if (count > 1) { 227 int taskInterval = Integer.parseInt(mTaskIntervalView.getText().toString()); 228 scheduleInfoBuilder.setPeriodic(Duration.ofSeconds(taskInterval)); 229 } 230 try { 231 Log.i(TAG, "Scheduling task to be executed"); 232 taskScheduler.scheduleTask(scheduleInfoBuilder.build()); 233 } catch (Exception e) { 234 Log.e(TAG, "Failed to schedule task: " + e); 235 return; 236 } 237 Log.i(TAG, "Task scheduled successfully"); 238 } 239 } 240