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