• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 package com.android.car.cluster;
17 
18 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE;
19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
20 
21 import android.car.Car;
22 import android.car.CarAppFocusManager;
23 import android.car.builtin.util.Slogf;
24 import android.car.cluster.navigation.NavigationState.Maneuver;
25 import android.car.cluster.navigation.NavigationState.NavigationStateProto;
26 import android.car.cluster.navigation.NavigationState.Step;
27 import android.car.cluster.renderer.IInstrumentClusterNavigation;
28 import android.car.navigation.CarNavigationInstrumentCluster;
29 import android.content.Context;
30 import android.os.Binder;
31 import android.os.Bundle;
32 import android.util.Log;
33 
34 import com.android.car.AppFocusService;
35 import com.android.car.AppFocusService.FocusOwnershipCallback;
36 import com.android.car.CarLocalServices;
37 import com.android.car.CarLog;
38 import com.android.car.CarServiceBase;
39 import com.android.car.CarServiceUtils;
40 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
41 import com.android.car.internal.util.IndentingPrintWriter;
42 import com.android.internal.annotations.GuardedBy;
43 import com.android.internal.annotations.VisibleForTesting;
44 
45 import com.google.protobuf.InvalidProtocolBufferException;
46 
47 import java.util.Objects;
48 
49 /**
50  * Service responsible for Navigation focus management and {@code NavigationState} change.
51  *
52  * @hide
53  */
54 public class ClusterNavigationService extends IInstrumentClusterNavigation.Stub
55         implements CarServiceBase, FocusOwnershipCallback {
56 
57     @VisibleForTesting
58     static final String TAG = CarLog.TAG_CLUSTER;
59 
60     private static final ContextOwner NO_OWNER = new ContextOwner(0, 0);
61     private static final String NAV_STATE_PROTO_BUNDLE_KEY = "navstate2";
62 
63     private final Context mContext;
64     private final AppFocusService mAppFocusService;
65 
66     private final Object mLock = new Object();
67 
68     @GuardedBy("mLock")
69     private ContextOwner mNavContextOwner = NO_OWNER;
70 
71     interface ClusterNavigationServiceCallback {
onNavigationStateChanged(Bundle bundle)72         void onNavigationStateChanged(Bundle bundle);
73 
getInstrumentClusterInfo()74         CarNavigationInstrumentCluster getInstrumentClusterInfo();
75 
notifyNavContextOwnerChanged(ContextOwner owner)76         void notifyNavContextOwnerChanged(ContextOwner owner);
77     }
78 
79     @GuardedBy("mLock")
80     ClusterNavigationServiceCallback mClusterServiceCallback;
81 
82     @Override
onNavigationStateChanged(Bundle bundle)83     public void onNavigationStateChanged(Bundle bundle) {
84         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_CAR_NAVIGATION_MANAGER);
85         assertNavigationFocus();
86         assertNavStateProtoValid(bundle);
87         ClusterNavigationServiceCallback callback;
88         synchronized (mLock) {
89             callback = mClusterServiceCallback;
90         }
91         if (callback == null) return;
92         callback.onNavigationStateChanged(bundle);
93     }
94 
95     @Override
getInstrumentClusterInfo()96     public CarNavigationInstrumentCluster getInstrumentClusterInfo() {
97         CarServiceUtils.assertPermission(mContext, Car.PERMISSION_CAR_NAVIGATION_MANAGER);
98         ClusterNavigationServiceCallback callback;
99         synchronized (mLock) {
100             callback = mClusterServiceCallback;
101         }
102         if (callback == null) return null;
103         return callback.getInstrumentClusterInfo();
104     }
105 
ClusterNavigationService(Context context, AppFocusService appFocusService)106     public ClusterNavigationService(Context context, AppFocusService appFocusService) {
107         mContext = context;
108         mAppFocusService = appFocusService;
109     }
110 
setClusterServiceCallback( ClusterNavigationServiceCallback clusterServiceCallback)111     public void setClusterServiceCallback(
112             ClusterNavigationServiceCallback clusterServiceCallback) {
113         synchronized (mLock) {
114             mClusterServiceCallback = clusterServiceCallback;
115         }
116     }
117 
118     @Override
init()119     public void init() {
120         if (Slogf.isLoggable(TAG, Log.DEBUG)) {
121             Slogf.d(TAG, "initClusterNavigationService");
122         }
123         mAppFocusService.registerContextOwnerChangedCallback(this /* FocusOwnershipCallback */);
124     }
125 
126     @Override
release()127     public void release() {
128         if (Slogf.isLoggable(TAG, Log.DEBUG)) {
129             Slogf.d(TAG, "releaseClusterNavigationService");
130         }
131         setClusterServiceCallback(null);
132         mAppFocusService.unregisterContextOwnerChangedCallback(this);
133     }
134 
135     @Override
136     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)137     public void dump(IndentingPrintWriter writer) {
138         writer.println("**" + getClass().getSimpleName() + "**");
139         synchronized (mLock) {
140             writer.println("context owner: " + mNavContextOwner);
141         }
142     }
143 
144     @Override
onFocusAcquired(int appType, int uid, int pid)145     public void onFocusAcquired(int appType, int uid, int pid) {
146         changeNavContextOwner(appType, uid, pid, true);
147     }
148 
149     @Override
onFocusAbandoned(int appType, int uid, int pid)150     public void onFocusAbandoned(int appType, int uid, int pid) {
151         changeNavContextOwner(appType, uid, pid, false);
152     }
153 
assertNavigationFocus()154     private void assertNavigationFocus() {
155         int uid = Binder.getCallingUid();
156         int pid = Binder.getCallingPid();
157         synchronized (mLock) {
158             if (uid == mNavContextOwner.uid && pid == mNavContextOwner.pid) {
159                 return;
160             }
161         }
162         // Stored one failed. There can be a delay, so check with real one again.
163         AppFocusService afs = CarLocalServices.getService(AppFocusService.class);
164         if (afs != null && afs.isFocusOwner(uid, pid,
165                 CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION)) {
166             return;
167         }
168         throw new IllegalStateException("Client not owning APP_FOCUS_TYPE_NAVIGATION");
169     }
170 
changeNavContextOwner(int appType, int uid, int pid, boolean acquire)171     private void changeNavContextOwner(int appType, int uid, int pid, boolean acquire) {
172         if (appType != CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION) {
173             return;
174         }
175         ContextOwner requester = new ContextOwner(uid, pid);
176         ContextOwner newOwner = acquire ? requester : NO_OWNER;
177         ClusterNavigationServiceCallback callback;
178         synchronized (mLock) {
179             if ((acquire && Objects.equals(mNavContextOwner, requester))
180                     || (!acquire && !Objects.equals(mNavContextOwner, requester))) {
181                 // Nothing to do here. Either the same owner is acquiring twice, or someone is
182                 // abandoning a focus they didn't have.
183                 Slogf.w(TAG, "Invalid nav context owner change (acquiring: " + acquire
184                         + "), current owner: [" + mNavContextOwner
185                         + "], requester: [" + requester + "]");
186                 return;
187             }
188 
189             mNavContextOwner = newOwner;
190             callback = mClusterServiceCallback;
191         }
192         if (callback == null) return;
193 
194         callback.notifyNavContextOwnerChanged(newOwner);
195     }
196 
assertNavStateProtoValid(Bundle bundle)197     private void assertNavStateProtoValid(Bundle bundle) {
198         byte[] protoBytes = bundle.getByteArray(NAV_STATE_PROTO_BUNDLE_KEY);
199         if (protoBytes == null) {
200             throw new IllegalArgumentException("Received navigation state byte array is null.");
201         }
202         try {
203             NavigationStateProto navigationStateProto = NavigationStateProto.parseFrom(protoBytes);
204             if (navigationStateProto.getStepsCount() == 0) {
205                 return;
206             }
207             for (Step step : navigationStateProto.getStepsList()) {
208                 Maneuver maneuver = step.getManeuver();
209                 if (!Maneuver.TypeV2.UNKNOWN_V2.equals(maneuver.getTypeV2())
210                         && Maneuver.Type.UNKNOWN.equals(maneuver.getType())) {
211                     throw new IllegalArgumentException(
212                         "Maneuver#type must be populated if Maneuver#typeV2 is also populated.");
213                 }
214             }
215         } catch (InvalidProtocolBufferException e) {
216             throw new IllegalArgumentException("Error parsing navigation state proto", e);
217         }
218     }
219 
220     static class ContextOwner {
221         final int uid;
222         final int pid;
223 
ContextOwner(int uid, int pid)224         ContextOwner(int uid, int pid) {
225             this.uid = uid;
226             this.pid = pid;
227         }
228 
229         @Override
toString()230         public String toString() {
231             return "uid: " + uid + ", pid: " + pid;
232         }
233 
234         @Override
equals(Object o)235         public boolean equals(Object o) {
236             if (this == o) return true;
237             if (o == null || getClass() != o.getClass()) return false;
238             ContextOwner that = (ContextOwner) o;
239             return uid == that.uid && pid == that.pid;
240         }
241 
242         @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE)
243         @Override
hashCode()244         public int hashCode() {
245             return Objects.hash(uid, pid);
246         }
247     }
248 }
249