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