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