ronnie
2022-10-23 d7a691c7a2527f2da145355a40a0402c95c67aac
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.server.infra;
 
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
import android.annotation.UserIdInt;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.TimeUtils;
 
import com.android.internal.annotations.GuardedBy;
 
import java.io.PrintWriter;
 
/**
 * Gets the service name using a framework resources, temporarily changing the service if necessary
 * (typically during CTS tests or service development).
 *
 * @hide
 */
public final class FrameworkResourcesServiceNameResolver implements ServiceNameResolver {
 
    private static final String TAG = FrameworkResourcesServiceNameResolver.class.getSimpleName();
 
    /** Handler message to {@link #resetTemporaryService(int)} */
    private static final int MSG_RESET_TEMPORARY_SERVICE = 0;
 
    private final @NonNull Context mContext;
    private final @NonNull Object mLock = new Object();
    private final @StringRes int mResourceId;
    private @Nullable NameResolverListener mOnSetCallback;
 
    /**
     * Map of temporary service name set by {@link #setTemporaryService(int, String, int)},
     * keyed by {@code userId}.
     *
     * <p>Typically used by Shell command and/or CTS tests.
     */
    @GuardedBy("mLock")
    private final SparseArray<String> mTemporaryServiceNames = new SparseArray<>();
 
    /**
     * Map of default services that have been disabled by
     * {@link #setDefaultServiceEnabled(int, boolean)},keyed by {@code userId}.
     *
     * <p>Typically used by Shell command and/or CTS tests.
     */
    @GuardedBy("mLock")
    private final SparseBooleanArray mDefaultServicesDisabled = new SparseBooleanArray();
 
    /**
     * When the temporary service will expire (and reset back to the default).
     */
    @GuardedBy("mLock")
    private long mTemporaryServiceExpiration;
 
    /**
     * Handler used to reset the temporary service name.
     */
    @GuardedBy("mLock")
    private Handler mTemporaryHandler;
 
    public FrameworkResourcesServiceNameResolver(@NonNull Context context,
            @StringRes int resourceId) {
        mContext = context;
        mResourceId = resourceId;
    }
 
    @Override
    public void setOnTemporaryServiceNameChangedCallback(@NonNull NameResolverListener callback) {
        synchronized (mLock) {
            this.mOnSetCallback = callback;
        }
    }
 
    @Override
    public String getDefaultServiceName(@UserIdInt int userId) {
        synchronized (mLock) {
            final String name = mContext.getString(mResourceId);
            return TextUtils.isEmpty(name) ? null : name;
        }
    }
 
    @Override
    public String getServiceName(@UserIdInt int userId) {
        synchronized (mLock) {
            final String temporaryName = mTemporaryServiceNames.get(userId);
            if (temporaryName != null) {
                // Always log it, as it should only be used on CTS or during development
                Slog.w(TAG, "getServiceName(): using temporary name " + temporaryName
                        + " for user " + userId);
                return temporaryName;
            }
            final boolean disabled = mDefaultServicesDisabled.get(userId);
            if (disabled) {
                // Always log it, as it should only be used on CTS or during development
                Slog.w(TAG, "getServiceName(): temporary name not set and default disabled for "
                        + "user " + userId);
                return null;
            }
            return getDefaultServiceName(userId);
        }
    }
 
    @Override
    public boolean isTemporary(@UserIdInt int userId) {
        synchronized (mLock) {
            return mTemporaryServiceNames.get(userId) != null;
        }
    }
 
    @Override
    public void setTemporaryService(@UserIdInt int userId, @NonNull String componentName,
            int durationMs) {
        synchronized (mLock) {
            mTemporaryServiceNames.put(userId, componentName);
 
            if (mTemporaryHandler == null) {
                mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
                    @Override
                    public void handleMessage(Message msg) {
                        if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
                            synchronized (mLock) {
                                resetTemporaryService(userId);
                            }
                        } else {
                            Slog.wtf(TAG, "invalid handler msg: " + msg);
                        }
                    }
                };
            } else {
                mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
            }
            mTemporaryServiceExpiration = SystemClock.elapsedRealtime() + durationMs;
            mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs);
            notifyTemporaryServiceNameChangedLocked(userId, componentName,
                    /* isTemporary= */ true);
        }
    }
 
    @Override
    public void resetTemporaryService(@UserIdInt int userId) {
        synchronized (mLock) {
            Slog.i(TAG, "resetting temporary service for user " + userId + " from "
                    + mTemporaryServiceNames.get(userId));
            mTemporaryServiceNames.remove(userId);
            if (mTemporaryHandler != null) {
                mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
                mTemporaryHandler = null;
            }
            notifyTemporaryServiceNameChangedLocked(userId, /* newTemporaryName= */ null,
                    /* isTemporary= */ false);
        }
    }
 
    @Override
    public boolean setDefaultServiceEnabled(int userId, boolean enabled) {
        synchronized (mLock) {
            final boolean currentlyEnabled = isDefaultServiceEnabledLocked(userId);
            if (currentlyEnabled == enabled) {
                Slog.i(TAG, "setDefaultServiceEnabled(" + userId + "): already " + enabled);
                return false;
            }
            if (enabled) {
                Slog.i(TAG, "disabling default service for user " + userId);
                mDefaultServicesDisabled.removeAt(userId);
            } else {
                Slog.i(TAG, "enabling default service for user " + userId);
                mDefaultServicesDisabled.put(userId, true);
            }
        }
        return true;
    }
 
    @Override
    public boolean isDefaultServiceEnabled(int userId) {
        synchronized (mLock) {
            return isDefaultServiceEnabledLocked(userId);
        }
    }
 
    private boolean isDefaultServiceEnabledLocked(int userId) {
        return !mDefaultServicesDisabled.get(userId);
    }
 
    @Override
    public String toString() {
        return "FrameworkResourcesServiceNamer[temps=" + mTemporaryServiceNames + "]";
    }
 
    // TODO(b/117779333): support proto
    @Override
    public void dumpShort(@NonNull PrintWriter pw) {
        synchronized (mLock) {
            pw.print("FrameworkResourcesServiceNamer: resId="); pw.print(mResourceId);
            pw.print(", numberTemps="); pw.print(mTemporaryServiceNames.size());
            pw.print(", enabledDefaults="); pw.print(mDefaultServicesDisabled.size());
        }
    }
 
    // TODO(b/117779333): support proto
    @Override
    public void dumpShort(@NonNull PrintWriter pw, @UserIdInt int userId) {
        synchronized (mLock) {
            final String temporaryName = mTemporaryServiceNames.get(userId);
            if (temporaryName != null) {
                pw.print("tmpName="); pw.print(temporaryName);
                final long ttl = mTemporaryServiceExpiration - SystemClock.elapsedRealtime();
                pw.print(" (expires in "); TimeUtils.formatDuration(ttl, pw); pw.print("), ");
            }
            pw.print("defaultName="); pw.print(getDefaultServiceName(userId));
            final boolean disabled = mDefaultServicesDisabled.get(userId);
            pw.println(disabled ? " (disabled)" : " (enabled)");
        }
    }
 
    private void notifyTemporaryServiceNameChangedLocked(@UserIdInt int userId,
            @Nullable String newTemporaryName, boolean isTemporary) {
        if (mOnSetCallback != null) {
            mOnSetCallback.onNameResolved(userId, newTemporaryName, isTemporary);
        }
    }
}