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