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