/*
* Copyright (C) 2014 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.trust; import android.Manifest;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustListener;
import android.app.trust.ITrustManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
import android.os.Binder;
import android.os.Build;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
import android.provider.Settings;
import android.service.trust.TrustAgentService;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.Xml;
import android.view.IWindowManager;
import android.view.WindowManagerGlobal;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.SystemService;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException; /**
* Manages trust agents and trust listeners.
*
* It is responsible for binding to the enabled {@link android.service.trust.TrustAgentService}s
* of each user and notifies them about events that are relevant to them.
* It start and stops them based on the value of
* {@link com.android.internal.widget.LockPatternUtils#getEnabledTrustAgents(int)}.
*
* It also keeps a set of {@link android.app.trust.ITrustListener}s that are notified whenever the
* trust state changes for any user.
*
* Trust state and the setting of enabled agents is kept per user and each user has its own
* instance of a {@link android.service.trust.TrustAgentService}.
*/
public class TrustManagerService extends SystemService {
private static final String TAG = "TrustManagerService";
static final boolean DEBUG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.VERBOSE); private static final Intent TRUST_AGENT_INTENT =
new Intent(TrustAgentService.SERVICE_INTERFACE);
private static final String PERMISSION_PROVIDE_AGENT = Manifest.permission.PROVIDE_TRUST_AGENT; private static final int MSG_REGISTER_LISTENER = 1;
private static final int MSG_UNREGISTER_LISTENER = 2;
private static final int MSG_DISPATCH_UNLOCK_ATTEMPT = 3;
private static final int MSG_ENABLED_AGENTS_CHANGED = 4;
private static final int MSG_KEYGUARD_SHOWING_CHANGED = 6;
private static final int MSG_START_USER = 7;
private static final int MSG_CLEANUP_USER = 8;
private static final int MSG_SWITCH_USER = 9;
private static final int MSG_FLUSH_TRUST_USUALLY_MANAGED = 10;
private static final int MSG_UNLOCK_USER = 11;
private static final int MSG_STOP_USER = 12;
private static final int MSG_DISPATCH_UNLOCK_LOCKOUT = 13;
private static final int MSG_REFRESH_DEVICE_LOCKED_FOR_USER = 14; private static final int TRUST_USUALLY_MANAGED_FLUSH_DELAY = 2 * 60 * 1000; private final ArraySet<AgentInfo> mActiveAgents = new ArraySet<>();
private final ArrayList<ITrustListener> mTrustListeners = new ArrayList<>();
private final Receiver mReceiver = new Receiver(); /* package */ final TrustArchive mArchive = new TrustArchive();
private final Context mContext;
private final LockPatternUtils mLockPatternUtils;
private final UserManager mUserManager;
private final ActivityManager mActivityManager; @GuardedBy("mUserIsTrusted")
private final SparseBooleanArray mUserIsTrusted = new SparseBooleanArray(); @GuardedBy("mDeviceLockedForUser")
private final SparseBooleanArray mDeviceLockedForUser = new SparseBooleanArray(); @GuardedBy("mTrustUsuallyManagedForUser")
private final SparseBooleanArray mTrustUsuallyManagedForUser = new SparseBooleanArray(); // set to true only if user can skip bouncer
@GuardedBy("mUsersUnlockedByFingerprint")
private final SparseBooleanArray mUsersUnlockedByFingerprint = new SparseBooleanArray(); private final StrongAuthTracker mStrongAuthTracker; private boolean mTrustAgentsCanRun = false;
private int mCurrentUser = UserHandle.USER_SYSTEM; public TrustManagerService(Context context) {
super(context);
mContext = context;
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
mLockPatternUtils = new LockPatternUtils(context);
mStrongAuthTracker = new StrongAuthTracker(context);
} @Override
public void onStart() {
publishBinderService(Context.TRUST_SERVICE, mService);
} @Override
public void onBootPhase(int phase) {
if (isSafeMode()) {
// No trust agents in safe mode.
return;
}
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
mPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
mReceiver.register(mContext);
mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker);
} else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
mTrustAgentsCanRun = true;
refreshAgentList(UserHandle.USER_ALL);
refreshDeviceLockedForUser(UserHandle.USER_ALL);
} else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
maybeEnableFactoryTrustAgents(mLockPatternUtils, UserHandle.USER_SYSTEM);
}
} // Agent management private static final class AgentInfo {
CharSequence label;
Drawable icon;
ComponentName component; // service that implements ITrustAgent
SettingsAttrs settings; // setting to launch to modify agent.
TrustAgentWrapper agent;
int userId; @Override
public boolean equals(Object other) {
if (!(other instanceof AgentInfo)) {
return false;
}
AgentInfo o = (AgentInfo) other;
return component.equals(o.component) && userId == o.userId;
} @Override
public int hashCode() {
return component.hashCode() * 31 + userId;
}
} private void updateTrustAll() {
List<UserInfo> userInfos = mUserManager.getUsers(true /* excludeDying */);
for (UserInfo userInfo : userInfos) {
updateTrust(userInfo.id, 0);
}
} public void updateTrust(int userId, int flags) {
boolean managed = aggregateIsTrustManaged(userId);
dispatchOnTrustManagedChanged(managed, userId);
if (mStrongAuthTracker.isTrustAllowedForUser(userId)
&& isTrustUsuallyManagedInternal(userId) != managed) {
updateTrustUsuallyManaged(userId, managed);
}
boolean trusted = aggregateIsTrusted(userId);
boolean changed;
synchronized (mUserIsTrusted) {
changed = mUserIsTrusted.get(userId) != trusted;
mUserIsTrusted.put(userId, trusted);
}
dispatchOnTrustChanged(trusted, userId, flags);
if (changed) {
refreshDeviceLockedForUser(userId);
}
} private void updateTrustUsuallyManaged(int userId, boolean managed) {
synchronized (mTrustUsuallyManagedForUser) {
mTrustUsuallyManagedForUser.put(userId, managed);
}
// Wait a few minutes before committing to flash, in case the trust agent is transiently not
// managing trust (crashed, needs to acknowledge DPM restrictions, etc).
mHandler.removeMessages(MSG_FLUSH_TRUST_USUALLY_MANAGED);
mHandler.sendMessageDelayed(
mHandler.obtainMessage(MSG_FLUSH_TRUST_USUALLY_MANAGED),
TRUST_USUALLY_MANAGED_FLUSH_DELAY);
} public long addEscrowToken(byte[] token, int userId) {
return mLockPatternUtils.addEscrowToken(token, userId);
} public boolean removeEscrowToken(long handle, int userId) {
return mLockPatternUtils.removeEscrowToken(handle, userId);
} public boolean isEscrowTokenActive(long handle, int userId) {
return mLockPatternUtils.isEscrowTokenActive(handle, userId);
} public void unlockUserWithToken(long handle, byte[] token, int userId) {
mLockPatternUtils.unlockUserWithToken(handle, token, userId);
} void showKeyguardErrorMessage(CharSequence message) {
dispatchOnTrustError(message);
} void refreshAgentList(int userIdOrAll) {
if (DEBUG) Slog.d(TAG, "refreshAgentList(" + userIdOrAll + ")");
if (!mTrustAgentsCanRun) {
return;
}
if (userIdOrAll != UserHandle.USER_ALL && userIdOrAll < UserHandle.USER_SYSTEM) {
Log.e(TAG, "refreshAgentList(userId=" + userIdOrAll + "): Invalid user handle,"
+ " must be USER_ALL or a specific user.", new Throwable("here"));
userIdOrAll = UserHandle.USER_ALL;
}
PackageManager pm = mContext.getPackageManager(); List<UserInfo> userInfos;
if (userIdOrAll == UserHandle.USER_ALL) {
userInfos = mUserManager.getUsers(true /* excludeDying */);
} else {
userInfos = new ArrayList<>();
userInfos.add(mUserManager.getUserInfo(userIdOrAll));
}
LockPatternUtils lockPatternUtils = mLockPatternUtils; ArraySet<AgentInfo> obsoleteAgents = new ArraySet<>();
obsoleteAgents.addAll(mActiveAgents); for (UserInfo userInfo : userInfos) {
if (userInfo == null || userInfo.partial || !userInfo.isEnabled()
|| userInfo.guestToRemove) continue;
if (!userInfo.supportsSwitchToByUser()) {
if (DEBUG) Slog.d(TAG, "refreshAgentList: skipping user " + userInfo.id
+ ": switchToByUser=false");
continue;
}
if (!mActivityManager.isUserRunning(userInfo.id)) {
if (DEBUG) Slog.d(TAG, "refreshAgentList: skipping user " + userInfo.id
+ ": user not started");
continue;
}
if (!lockPatternUtils.isSecure(userInfo.id)) {
if (DEBUG) Slog.d(TAG, "refreshAgentList: skipping user " + userInfo.id
+ ": no secure credential");
continue;
} DevicePolicyManager dpm = lockPatternUtils.getDevicePolicyManager();
int disabledFeatures = dpm.getKeyguardDisabledFeatures(null, userInfo.id);
final boolean disableTrustAgents =
(disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS) != 0; List<ComponentName> enabledAgents = lockPatternUtils.getEnabledTrustAgents(userInfo.id);
if (enabledAgents == null) {
if (DEBUG) Slog.d(TAG, "refreshAgentList: skipping user " + userInfo.id
+ ": no agents enabled by user");
continue;
}
List<ResolveInfo> resolveInfos = resolveAllowedTrustAgents(pm, userInfo.id);
for (ResolveInfo resolveInfo : resolveInfos) {
ComponentName name = getComponentName(resolveInfo); if (!enabledAgents.contains(name)) {
if (DEBUG) Slog.d(TAG, "refreshAgentList: skipping "
+ name.flattenToShortString() + " u"+ userInfo.id
+ ": not enabled by user");
continue;
}
if (disableTrustAgents) {
List<PersistableBundle> config =
dpm.getTrustAgentConfiguration(null /* admin */, name, userInfo.id);
// Disable agent if no features are enabled.
if (config == null || config.isEmpty()) {
if (DEBUG) Slog.d(TAG, "refreshAgentList: skipping "
+ name.flattenToShortString() + " u"+ userInfo.id
+ ": not allowed by DPM");
continue;
}
}
AgentInfo agentInfo = new AgentInfo();
agentInfo.component = name;
agentInfo.userId = userInfo.id;
if (!mActiveAgents.contains(agentInfo)) {
agentInfo.label = resolveInfo.loadLabel(pm);
agentInfo.icon = resolveInfo.loadIcon(pm);
agentInfo.settings = getSettingsAttrs(pm, resolveInfo);
} else {
int index = mActiveAgents.indexOf(agentInfo);
agentInfo = mActiveAgents.valueAt(index);
} boolean directUnlock = resolveInfo.serviceInfo.directBootAware
&& agentInfo.settings.canUnlockProfile; if (directUnlock) {
if (DEBUG) Slog.d(TAG, "refreshAgentList: trustagent " + name
+ "of user " + userInfo.id + "can unlock user profile.");
} if (!mUserManager.isUserUnlockingOrUnlocked(userInfo.id)
&& !directUnlock) {
if (DEBUG) Slog.d(TAG, "refreshAgentList: skipping user " + userInfo.id
+ "'s trust agent " + name + ": FBE still locked and "
+ " the agent cannot unlock user profile.");
continue;
} if (!mStrongAuthTracker.canAgentsRunForUser(userInfo.id)) {
int flag = mStrongAuthTracker.getStrongAuthForUser(userInfo.id);
if (flag != StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) {
if (flag != StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
|| !directUnlock) {
if (DEBUG)
Slog.d(TAG, "refreshAgentList: skipping user " + userInfo.id
+ ": prevented by StrongAuthTracker = 0x"
+ Integer.toHexString(mStrongAuthTracker.getStrongAuthForUser(
userInfo.id)));
continue;
}
}
} if (agentInfo.agent == null) {
agentInfo.agent = new TrustAgentWrapper(mContext, this,
new Intent().setComponent(name), userInfo.getUserHandle());
} if (!mActiveAgents.contains(agentInfo)) {
mActiveAgents.add(agentInfo);
} else {
obsoleteAgents.remove(agentInfo);
}
}
} boolean trustMayHaveChanged = false;
for (int i = 0; i < obsoleteAgents.size(); i++) {
AgentInfo info = obsoleteAgents.valueAt(i);
if (userIdOrAll == UserHandle.USER_ALL || userIdOrAll == info.userId) {
if (info.agent.isManagingTrust()) {
trustMayHaveChanged = true;
}
info.agent.destroy();
mActiveAgents.remove(info);
}
} if (trustMayHaveChanged) {
if (userIdOrAll == UserHandle.USER_ALL) {
updateTrustAll();
} else {
updateTrust(userIdOrAll, 0);
}
}
} boolean isDeviceLockedInner(int userId) {
synchronized (mDeviceLockedForUser) {
return mDeviceLockedForUser.get(userId, true);
}
} private void refreshDeviceLockedForUser(int userId) {
if (userId != UserHandle.USER_ALL && userId < UserHandle.USER_SYSTEM) {
Log.e(TAG, "refreshDeviceLockedForUser(userId=" + userId + "): Invalid user handle,"
+ " must be USER_ALL or a specific user.", new Throwable("here"));
userId = UserHandle.USER_ALL;
}
List<UserInfo> userInfos;
if (userId == UserHandle.USER_ALL) {
userInfos = mUserManager.getUsers(true /* excludeDying */);
} else {
userInfos = new ArrayList<>();
userInfos.add(mUserManager.getUserInfo(userId));
} IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); for (int i = 0; i < userInfos.size(); i++) {
UserInfo info = userInfos.get(i); if (info == null || info.partial || !info.isEnabled() || info.guestToRemove
|| !info.supportsSwitchToByUser()) {
continue;
} int id = info.id;
boolean secure = mLockPatternUtils.isSecure(id);
boolean trusted = aggregateIsTrusted(id);
boolean showingKeyguard = true;
boolean fingerprintAuthenticated = false; if (mCurrentUser == id) {
synchronized(mUsersUnlockedByFingerprint) {
fingerprintAuthenticated = mUsersUnlockedByFingerprint.get(id, false);
}
try {
showingKeyguard = wm.isKeyguardLocked();
} catch (RemoteException e) {
}
}
boolean deviceLocked = secure && showingKeyguard && !trusted &&
!fingerprintAuthenticated;
setDeviceLockedForUser(id, deviceLocked);
}
} private void setDeviceLockedForUser(@UserIdInt int userId, boolean locked) {
final boolean changed;
synchronized (mDeviceLockedForUser) {
changed = isDeviceLockedInner(userId) != locked;
mDeviceLockedForUser.put(userId, locked);
}
if (changed) {
dispatchDeviceLocked(userId, locked);
}
} private void dispatchDeviceLocked(int userId, boolean isLocked) {
for (int i = 0; i < mActiveAgents.size(); i++) {
AgentInfo agent = mActiveAgents.valueAt(i);
if (agent.userId == userId) {
if (isLocked) {
agent.agent.onDeviceLocked();
} else{
agent.agent.onDeviceUnlocked();
}
}
}
} void updateDevicePolicyFeatures() {
boolean changed = false;
for (int i = 0; i < mActiveAgents.size(); i++) {
AgentInfo info = mActiveAgents.valueAt(i);
if (info.agent.isConnected()) {
info.agent.updateDevicePolicyFeatures();
changed = true;
}
}
if (changed) {
mArchive.logDevicePolicyChanged();
}
} private void removeAgentsOfPackage(String packageName) {
boolean trustMayHaveChanged = false;
for (int i = mActiveAgents.size() - 1; i >= 0; i--) {
AgentInfo info = mActiveAgents.valueAt(i);
if (packageName.equals(info.component.getPackageName())) {
Log.i(TAG, "Resetting agent " + info.component.flattenToShortString());
if (info.agent.isManagingTrust()) {
trustMayHaveChanged = true;
}
info.agent.destroy();
mActiveAgents.removeAt(i);
}
}
if (trustMayHaveChanged) {
updateTrustAll();
}
} public void resetAgent(ComponentName name, int userId) {
boolean trustMayHaveChanged = false;
for (int i = mActiveAgents.size() - 1; i >= 0; i--) {
AgentInfo info = mActiveAgents.valueAt(i);
if (name.equals(info.component) && userId == info.userId) {
Log.i(TAG, "Resetting agent " + info.component.flattenToShortString());
if (info.agent.isManagingTrust()) {
trustMayHaveChanged = true;
}
info.agent.destroy();
mActiveAgents.removeAt(i);
}
}
if (trustMayHaveChanged) {
updateTrust(userId, 0);
}
refreshAgentList(userId);
} private SettingsAttrs getSettingsAttrs(PackageManager pm, ResolveInfo resolveInfo) {
if (resolveInfo == null || resolveInfo.serviceInfo == null
|| resolveInfo.serviceInfo.metaData == null) return null;
String cn = null;
boolean canUnlockProfile = false; XmlResourceParser parser = null;
Exception caughtException = null;
try {
parser = resolveInfo.serviceInfo.loadXmlMetaData(pm,
TrustAgentService.TRUST_AGENT_META_DATA);
if (parser == null) {
Slog.w(TAG, "Can't find " + TrustAgentService.TRUST_AGENT_META_DATA + " meta-data");
return null;
}
Resources res = pm.getResourcesForApplication(resolveInfo.serviceInfo.applicationInfo);
AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& type != XmlPullParser.START_TAG) {
// Drain preamble.
}
String nodeName = parser.getName();
if (!"trust-agent".equals(nodeName)) {
Slog.w(TAG, "Meta-data does not start with trust-agent tag");
return null;
}
TypedArray sa = res
.obtainAttributes(attrs, com.android.internal.R.styleable.TrustAgent);
cn = sa.getString(com.android.internal.R.styleable.TrustAgent_settingsActivity);
canUnlockProfile = sa.getBoolean(
com.android.internal.R.styleable.TrustAgent_unlockProfile, false);
sa.recycle();
} catch (PackageManager.NameNotFoundException e) {
caughtException = e;
} catch (IOException e) {
caughtException = e;
} catch (XmlPullParserException e) {
caughtException = e;
} finally {
if (parser != null) parser.close();
}
if (caughtException != null) {
Slog.w(TAG, "Error parsing : " + resolveInfo.serviceInfo.packageName, caughtException);
return null;
}
if (cn == null) {
return null;
}
if (cn.indexOf('/') < 0) {
cn = resolveInfo.serviceInfo.packageName + "/" + cn;
}
return new SettingsAttrs(ComponentName.unflattenFromString(cn), canUnlockProfile);
} private ComponentName getComponentName(ResolveInfo resolveInfo) {
if (resolveInfo == null || resolveInfo.serviceInfo == null) return null;
return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
} private void maybeEnableFactoryTrustAgents(LockPatternUtils utils, int userId) {
if (0 != Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.TRUST_AGENTS_INITIALIZED, 0, userId)) {
return;
}
PackageManager pm = mContext.getPackageManager();
List<ResolveInfo> resolveInfos = resolveAllowedTrustAgents(pm, userId);
ComponentName defaultAgent = getDefaultFactoryTrustAgent(mContext);
boolean shouldUseDefaultAgent = defaultAgent != null;
ArraySet<ComponentName> discoveredAgents = new ArraySet<>(); if (shouldUseDefaultAgent) {
discoveredAgents.add(defaultAgent);
Log.i(TAG, "Enabling " + defaultAgent + " because it is a default agent.");
} else { // A default agent is not set; perform regular trust agent discovery
for (ResolveInfo resolveInfo : resolveInfos) {
ComponentName componentName = getComponentName(resolveInfo);
int applicationInfoFlags = resolveInfo.serviceInfo.applicationInfo.flags;
if ((applicationInfoFlags & ApplicationInfo.FLAG_SYSTEM) == 0) {
Log.i(TAG, "Leaving agent " + componentName + " disabled because package "
+ "is not a system package.");
continue;
}
discoveredAgents.add(componentName);
}
} List<ComponentName> previouslyEnabledAgents = utils.getEnabledTrustAgents(userId);
if (previouslyEnabledAgents != null) {
discoveredAgents.addAll(previouslyEnabledAgents);
}
utils.setEnabledTrustAgents(discoveredAgents, userId);
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.TRUST_AGENTS_INITIALIZED, 1, userId);
} /**
* Returns the {@link ComponentName} for the default trust agent, or {@code null} if there
* is no trust agent set.
*/
private static ComponentName getDefaultFactoryTrustAgent(Context context) {
String defaultTrustAgent = context.getResources()
.getString(com.android.internal.R.string.config_defaultTrustAgent);
if (TextUtils.isEmpty(defaultTrustAgent)) {
return null;
}
return ComponentName.unflattenFromString(defaultTrustAgent);
} private List<ResolveInfo> resolveAllowedTrustAgents(PackageManager pm, int userId) {
List<ResolveInfo> resolveInfos = pm.queryIntentServicesAsUser(TRUST_AGENT_INTENT,
PackageManager.GET_META_DATA |
PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
userId);
ArrayList<ResolveInfo> allowedAgents = new ArrayList<>(resolveInfos.size());
for (ResolveInfo resolveInfo : resolveInfos) {
if (resolveInfo.serviceInfo == null) continue;
if (resolveInfo.serviceInfo.applicationInfo == null) continue;
String packageName = resolveInfo.serviceInfo.packageName;
if (pm.checkPermission(PERMISSION_PROVIDE_AGENT, packageName)
!= PackageManager.PERMISSION_GRANTED) {
ComponentName name = getComponentName(resolveInfo);
Log.w(TAG, "Skipping agent " + name + " because package does not have"
+ " permission " + PERMISSION_PROVIDE_AGENT + ".");
continue;
}
allowedAgents.add(resolveInfo);
}
return allowedAgents;
} // Agent dispatch and aggregation private boolean aggregateIsTrusted(int userId) {
if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) {
return false;
}
for (int i = 0; i < mActiveAgents.size(); i++) {
AgentInfo info = mActiveAgents.valueAt(i);
if (info.userId == userId) {
if (info.agent.isTrusted()) {
return true;
}
}
}
return false;
} private boolean aggregateIsTrustManaged(int userId) {
if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) {
return false;
}
for (int i = 0; i < mActiveAgents.size(); i++) {
AgentInfo info = mActiveAgents.valueAt(i);
if (info.userId == userId) {
if (info.agent.isManagingTrust()) {
return true;
}
}
}
return false;
} private void dispatchUnlockAttempt(boolean successful, int userId) {
if (successful) {
mStrongAuthTracker.allowTrustFromUnlock(userId);
} for (int i = 0; i < mActiveAgents.size(); i++) {
AgentInfo info = mActiveAgents.valueAt(i);
if (info.userId == userId) {
info.agent.onUnlockAttempt(successful);
}
}
} private void dispatchUnlockLockout(int timeoutMs, int userId) {
for (int i = 0; i < mActiveAgents.size(); i++) {
AgentInfo info = mActiveAgents.valueAt(i);
if (info.userId == userId) {
info.agent.onUnlockLockout(timeoutMs);
}
}
} // Listeners private void addListener(ITrustListener listener) {
for (int i = 0; i < mTrustListeners.size(); i++) {
if (mTrustListeners.get(i).asBinder() == listener.asBinder()) {
return;
}
}
mTrustListeners.add(listener);
updateTrustAll();
} private void removeListener(ITrustListener listener) {
for (int i = 0; i < mTrustListeners.size(); i++) {
if (mTrustListeners.get(i).asBinder() == listener.asBinder()) {
mTrustListeners.remove(i);
return;
}
}
} private void dispatchOnTrustChanged(boolean enabled, int userId, int flags) {
if (DEBUG) {
Log.i(TAG, "onTrustChanged(" + enabled + ", " + userId + ", 0x"
+ Integer.toHexString(flags) + ")");
}
if (!enabled) flags = 0;
for (int i = 0; i < mTrustListeners.size(); i++) {
try {
mTrustListeners.get(i).onTrustChanged(enabled, userId, flags);
} catch (DeadObjectException e) {
Slog.d(TAG, "Removing dead TrustListener.");
mTrustListeners.remove(i);
i--;
} catch (RemoteException e) {
Slog.e(TAG, "Exception while notifying TrustListener.", e);
}
}
} private void dispatchOnTrustManagedChanged(boolean managed, int userId) {
if (DEBUG) {
Log.i(TAG, "onTrustManagedChanged(" + managed + ", " + userId + ")");
}
for (int i = 0; i < mTrustListeners.size(); i++) {
try {
mTrustListeners.get(i).onTrustManagedChanged(managed, userId);
} catch (DeadObjectException e) {
Slog.d(TAG, "Removing dead TrustListener.");
mTrustListeners.remove(i);
i--;
} catch (RemoteException e) {
Slog.e(TAG, "Exception while notifying TrustListener.", e);
}
}
} private void dispatchOnTrustError(CharSequence message) {
if (DEBUG) {
Log.i(TAG, "onTrustError(" + message + ")");
}
for (int i = 0; i < mTrustListeners.size(); i++) {
try {
mTrustListeners.get(i).onTrustError(message);
} catch (DeadObjectException e) {
Slog.d(TAG, "Removing dead TrustListener.");
mTrustListeners.remove(i);
i--;
} catch (RemoteException e) {
Slog.e(TAG, "Exception while notifying TrustListener.", e);
}
}
} // User lifecycle @Override
public void onStartUser(int userId) {
mHandler.obtainMessage(MSG_START_USER, userId, 0, null).sendToTarget();
} @Override
public void onCleanupUser(int userId) {
mHandler.obtainMessage(MSG_CLEANUP_USER, userId, 0, null).sendToTarget();
} @Override
public void onSwitchUser(int userId) {
mHandler.obtainMessage(MSG_SWITCH_USER, userId, 0, null).sendToTarget();
} @Override
public void onUnlockUser(int userId) {
mHandler.obtainMessage(MSG_UNLOCK_USER, userId, 0, null).sendToTarget();
} @Override
public void onStopUser(@UserIdInt int userId) {
mHandler.obtainMessage(MSG_STOP_USER, userId, 0, null).sendToTarget();
} // Plumbing private final IBinder mService = new ITrustManager.Stub() {
@Override
public void reportUnlockAttempt(boolean authenticated, int userId) throws RemoteException {
enforceReportPermission();
mHandler.obtainMessage(MSG_DISPATCH_UNLOCK_ATTEMPT, authenticated ? 1 : 0, userId)
.sendToTarget();
} @Override
public void reportUnlockLockout(int timeoutMs, int userId) throws RemoteException {
enforceReportPermission();
mHandler.obtainMessage(MSG_DISPATCH_UNLOCK_LOCKOUT, timeoutMs, userId)
.sendToTarget();
} @Override
public void reportEnabledTrustAgentsChanged(int userId) throws RemoteException {
enforceReportPermission();
// coalesce refresh messages.
mHandler.removeMessages(MSG_ENABLED_AGENTS_CHANGED);
mHandler.sendEmptyMessage(MSG_ENABLED_AGENTS_CHANGED);
} @Override
public void reportKeyguardShowingChanged() throws RemoteException {
enforceReportPermission();
// coalesce refresh messages.
mHandler.removeMessages(MSG_KEYGUARD_SHOWING_CHANGED);
mHandler.sendEmptyMessage(MSG_KEYGUARD_SHOWING_CHANGED); // Make sure handler processes the message before returning, such that isDeviceLocked
// after this call will retrieve the correct value.
mHandler.runWithScissors(() -> {}, 0);
} @Override
public void registerTrustListener(ITrustListener trustListener) throws RemoteException {
enforceListenerPermission();
mHandler.obtainMessage(MSG_REGISTER_LISTENER, trustListener).sendToTarget();
} @Override
public void unregisterTrustListener(ITrustListener trustListener) throws RemoteException {
enforceListenerPermission();
mHandler.obtainMessage(MSG_UNREGISTER_LISTENER, trustListener).sendToTarget();
} @Override
public boolean isDeviceLocked(int userId) throws RemoteException {
userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
false /* allowAll */, true /* requireFull */, "isDeviceLocked", null); long token = Binder.clearCallingIdentity();
try {
if (!mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
userId = resolveProfileParent(userId);
}
return isDeviceLockedInner(userId);
} finally {
Binder.restoreCallingIdentity(token);
}
} @Override
public boolean isDeviceSecure(int userId) throws RemoteException {
userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
false /* allowAll */, true /* requireFull */, "isDeviceSecure", null); long token = Binder.clearCallingIdentity();
try {
if (!mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
userId = resolveProfileParent(userId);
}
return mLockPatternUtils.isSecure(userId);
} finally {
Binder.restoreCallingIdentity(token);
}
} private void enforceReportPermission() {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE, "reporting trust events");
} private void enforceListenerPermission() {
mContext.enforceCallingPermission(Manifest.permission.TRUST_LISTENER,
"register trust listener");
} @Override
protected void dump(FileDescriptor fd, final PrintWriter fout, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, fout)) return;
if (isSafeMode()) {
fout.println("disabled because the system is in safe mode.");
return;
}
if (!mTrustAgentsCanRun) {
fout.println("disabled because the third-party apps can't run yet.");
return;
}
final List<UserInfo> userInfos = mUserManager.getUsers(true /* excludeDying */);
mHandler.runWithScissors(new Runnable() {
@Override
public void run() {
fout.println("Trust manager state:");
for (UserInfo user : userInfos) {
dumpUser(fout, user, user.id == mCurrentUser);
}
}
}, 1500);
} private void dumpUser(PrintWriter fout, UserInfo user, boolean isCurrent) {
fout.printf(" User \"%s\" (id=%d, flags=%#x)",
user.name, user.id, user.flags);
if (!user.supportsSwitchToByUser()) {
fout.println("(managed profile)");
fout.println(" disabled because switching to this user is not possible.");
return;
}
if (isCurrent) {
fout.print(" (current)");
}
fout.print(": trusted=" + dumpBool(aggregateIsTrusted(user.id)));
fout.print(", trustManaged=" + dumpBool(aggregateIsTrustManaged(user.id)));
fout.print(", deviceLocked=" + dumpBool(isDeviceLockedInner(user.id)));
fout.print(", strongAuthRequired=" + dumpHex(
mStrongAuthTracker.getStrongAuthForUser(user.id)));
fout.println();
fout.println(" Enabled agents:");
boolean duplicateSimpleNames = false;
ArraySet<String> simpleNames = new ArraySet<String>();
for (AgentInfo info : mActiveAgents) {
if (info.userId != user.id) { continue; }
boolean trusted = info.agent.isTrusted();
fout.print(" "); fout.println(info.component.flattenToShortString());
fout.print(" bound=" + dumpBool(info.agent.isBound()));
fout.print(", connected=" + dumpBool(info.agent.isConnected()));
fout.print(", managingTrust=" + dumpBool(info.agent.isManagingTrust()));
fout.print(", trusted=" + dumpBool(trusted));
fout.println();
if (trusted) {
fout.println(" message=\"" + info.agent.getMessage() + "\"");
}
if (!info.agent.isConnected()) {
String restartTime = TrustArchive.formatDuration(
info.agent.getScheduledRestartUptimeMillis()
- SystemClock.uptimeMillis());
fout.println(" restartScheduledAt=" + restartTime);
}
if (!simpleNames.add(TrustArchive.getSimpleName(info.component))) {
duplicateSimpleNames = true;
}
}
fout.println(" Events:");
mArchive.dump(fout, 50, user.id, " " /* linePrefix */, duplicateSimpleNames);
fout.println();
} private String dumpBool(boolean b) {
return b ? "1" : "0";
} private String dumpHex(int i) {
return "0x" + Integer.toHexString(i);
} @Override
public void setDeviceLockedForUser(int userId, boolean locked) {
enforceReportPermission();
final long identity = Binder.clearCallingIdentity();
try {
if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
synchronized (mDeviceLockedForUser) {
mDeviceLockedForUser.put(userId, locked);
}
if (locked) {
try {
ActivityManager.getService().notifyLockedProfile(userId);
} catch (RemoteException e) {
}
}
final Intent lockIntent = new Intent(Intent.ACTION_DEVICE_LOCKED_CHANGED);
lockIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
lockIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
mContext.sendBroadcastAsUser(lockIntent, UserHandle.SYSTEM,
Manifest.permission.TRUST_LISTENER, /* options */ null);
}
} finally {
Binder.restoreCallingIdentity(identity);
}
} @Override
public boolean isTrustUsuallyManaged(int userId) {
mContext.enforceCallingPermission(Manifest.permission.TRUST_LISTENER,
"query trust state");
return isTrustUsuallyManagedInternal(userId);
} @Override
public void unlockedByFingerprintForUser(int userId) {
enforceReportPermission();
synchronized(mUsersUnlockedByFingerprint) {
mUsersUnlockedByFingerprint.put(userId, true);
}
mHandler.obtainMessage(MSG_REFRESH_DEVICE_LOCKED_FOR_USER, userId,
0 /* arg2 */).sendToTarget();
} @Override
public void clearAllFingerprints() {
enforceReportPermission();
synchronized(mUsersUnlockedByFingerprint) {
mUsersUnlockedByFingerprint.clear();
}
mHandler.obtainMessage(MSG_REFRESH_DEVICE_LOCKED_FOR_USER, UserHandle.USER_ALL,
0 /* arg2 */).sendToTarget();
}
}; private boolean isTrustUsuallyManagedInternal(int userId) {
synchronized (mTrustUsuallyManagedForUser) {
int i = mTrustUsuallyManagedForUser.indexOfKey(userId);
if (i >= 0) {
return mTrustUsuallyManagedForUser.valueAt(i);
}
}
// It's not in memory yet, get the value from persisted storage instead
boolean persistedValue = mLockPatternUtils.isTrustUsuallyManaged(userId);
synchronized (mTrustUsuallyManagedForUser) {
int i = mTrustUsuallyManagedForUser.indexOfKey(userId);
if (i >= 0) {
// Someone set the trust usually managed in the mean time. Better use that.
return mTrustUsuallyManagedForUser.valueAt(i);
} else {
// .. otherwise it's safe to cache the fetched value now.
mTrustUsuallyManagedForUser.put(userId, persistedValue);
return persistedValue;
}
}
} private int resolveProfileParent(int userId) {
long identity = Binder.clearCallingIdentity();
try {
UserInfo parent = mUserManager.getProfileParent(userId);
if (parent != null) {
return parent.getUserHandle().getIdentifier();
}
return userId;
} finally {
Binder.restoreCallingIdentity(identity);
}
} private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REGISTER_LISTENER:
addListener((ITrustListener) msg.obj);
break;
case MSG_UNREGISTER_LISTENER:
removeListener((ITrustListener) msg.obj);
break;
case MSG_DISPATCH_UNLOCK_ATTEMPT:
dispatchUnlockAttempt(msg.arg1 != 0, msg.arg2);
break;
case MSG_DISPATCH_UNLOCK_LOCKOUT:
dispatchUnlockLockout(msg.arg1, msg.arg2);
break;
case MSG_ENABLED_AGENTS_CHANGED:
refreshAgentList(UserHandle.USER_ALL);
// This is also called when the security mode of a user changes.
refreshDeviceLockedForUser(UserHandle.USER_ALL);
break;
case MSG_KEYGUARD_SHOWING_CHANGED:
refreshDeviceLockedForUser(mCurrentUser);
break;
case MSG_START_USER:
case MSG_CLEANUP_USER:
case MSG_UNLOCK_USER:
refreshAgentList(msg.arg1);
break;
case MSG_SWITCH_USER:
mCurrentUser = msg.arg1;
refreshDeviceLockedForUser(UserHandle.USER_ALL);
break;
case MSG_STOP_USER:
setDeviceLockedForUser(msg.arg1, true);
break;
case MSG_FLUSH_TRUST_USUALLY_MANAGED:
SparseBooleanArray usuallyManaged;
synchronized (mTrustUsuallyManagedForUser) {
usuallyManaged = mTrustUsuallyManagedForUser.clone();
} for (int i = 0; i < usuallyManaged.size(); i++) {
int userId = usuallyManaged.keyAt(i);
boolean value = usuallyManaged.valueAt(i);
if (value != mLockPatternUtils.isTrustUsuallyManaged(userId)) {
mLockPatternUtils.setTrustUsuallyManaged(value, userId);
}
}
break;
case MSG_REFRESH_DEVICE_LOCKED_FOR_USER:
refreshDeviceLockedForUser(msg.arg1);
break;
}
}
}; private final PackageMonitor mPackageMonitor = new PackageMonitor() {
@Override
public void onSomePackagesChanged() {
refreshAgentList(UserHandle.USER_ALL);
} @Override
public boolean onPackageChanged(String packageName, int uid, String[] components) {
// We're interested in all changes, even if just some components get enabled / disabled.
return true;
} @Override
public void onPackageDisappeared(String packageName, int reason) {
removeAgentsOfPackage(packageName);
}
}; private static class SettingsAttrs {
public ComponentName componentName;
public boolean canUnlockProfile; public SettingsAttrs(
ComponentName componentName,
boolean canUnlockProfile) {
this.componentName = componentName;
this.canUnlockProfile = canUnlockProfile;
}
}; private class Receiver extends BroadcastReceiver { @Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action)) {
refreshAgentList(getSendingUserId());
updateDevicePolicyFeatures();
} else if (Intent.ACTION_USER_ADDED.equals(action)) {
int userId = getUserId(intent);
if (userId > 0) {
maybeEnableFactoryTrustAgents(mLockPatternUtils, userId);
}
} else if (Intent.ACTION_USER_REMOVED.equals(action)) {
int userId = getUserId(intent);
if (userId > 0) {
synchronized (mUserIsTrusted) {
mUserIsTrusted.delete(userId);
}
synchronized (mDeviceLockedForUser) {
mDeviceLockedForUser.delete(userId);
}
synchronized (mTrustUsuallyManagedForUser) {
mTrustUsuallyManagedForUser.delete(userId);
}
synchronized (mUsersUnlockedByFingerprint) {
mUsersUnlockedByFingerprint.delete(userId);
}
refreshAgentList(userId);
refreshDeviceLockedForUser(userId);
}
}
} private int getUserId(Intent intent) {
int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -100);
if (userId > 0) {
return userId;
} else {
Slog.wtf(TAG, "EXTRA_USER_HANDLE missing or invalid, value=" + userId);
return -100;
}
} public void register(Context context) {
IntentFilter filter = new IntentFilter();
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
filter.addAction(Intent.ACTION_USER_ADDED);
filter.addAction(Intent.ACTION_USER_REMOVED);
context.registerReceiverAsUser(this,
UserHandle.ALL,
filter,
null /* permission */,
null /* scheduler */);
}
} private class StrongAuthTracker extends LockPatternUtils.StrongAuthTracker { SparseBooleanArray mStartFromSuccessfulUnlock = new SparseBooleanArray(); public StrongAuthTracker(Context context) {
super(context);
} @Override
public void onStrongAuthRequiredChanged(int userId) {
mStartFromSuccessfulUnlock.delete(userId); if (DEBUG) {
Log.i(TAG, "onStrongAuthRequiredChanged(" + userId + ") ->"
+ " trustAllowed=" + isTrustAllowedForUser(userId)
+ " agentsCanRun=" + canAgentsRunForUser(userId));
} refreshAgentList(userId); // The list of active trust agents may not have changed, if there was a previous call
// to allowTrustFromUnlock, so we update the trust here too.
updateTrust(userId, 0 /* flags */);
} boolean canAgentsRunForUser(int userId) {
return mStartFromSuccessfulUnlock.get(userId)
|| super.isTrustAllowedForUser(userId);
} /**
* Temporarily suppress strong auth requirements for {@param userId} until strong auth
* changes again. Must only be called when we know about a successful unlock already
* before the underlying StrongAuthTracker.
*
* Note that this only changes whether trust agents can be started, not the actual trusted
* value.
*/
void allowTrustFromUnlock(int userId) {
if (userId < UserHandle.USER_SYSTEM) {
throw new IllegalArgumentException("userId must be a valid user: " + userId);
}
boolean previous = canAgentsRunForUser(userId);
mStartFromSuccessfulUnlock.put(userId, true); if (DEBUG) {
Log.i(TAG, "allowTrustFromUnlock(" + userId + ") ->"
+ " trustAllowed=" + isTrustAllowedForUser(userId)
+ " agentsCanRun=" + canAgentsRunForUser(userId));
} if (canAgentsRunForUser(userId) != previous) {
refreshAgentList(userId);
}
}
}
}

TrustManagerService.java的更多相关文章

  1. Spark案例分析

    一.需求:计算网页访问量前三名 import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} /* ...

  2. 故障重现(内存篇2),JAVA内存不足导致频繁回收和swap引起的性能问题

    背景起因: 记起以前的另一次也是关于内存的调优分享下   有个系统平时运行非常稳定运行(没经历过大并发考验),然而在一次活动后,人数并发一上来后,系统开始卡. 我按经验开始调优,在每个关键步骤的加入如 ...

  3. Elasticsearch之java的基本操作一

    摘要   接触ElasticSearch已经有一段了.在这期间,遇到很多问题,但在最后自己的不断探索下解决了这些问题.看到网上或多或少的都有一些介绍ElasticSearch相关知识的文档,但个人觉得 ...

  4. 论:开发者信仰之“天下IT是一家“(Java .NET篇)

    比尔盖茨公认的IT界领军人物,打造了辉煌一时的PC时代. 2008年,史蒂夫鲍尔默接替了盖茨的工作,成为微软公司的总裁. 2013年他与微软做了最后的道别. 2013年以后,我才真正看到了微软的变化. ...

  5. 故障重现, JAVA进程内存不够时突然挂掉模拟

    背景,服务器上的一个JAVA服务进程突然挂掉,查看产生了崩溃日志,如下: # Set larger code cache with -XX:ReservedCodeCacheSize= # This ...

  6. 死磕内存篇 --- JAVA进程和linux内存间的大小关系

    运行个JAVA 用sleep去hold住 package org.hjb.test; public class TestOnly { public static void main(String[] ...

  7. 【小程序分享篇 一 】开发了个JAVA小程序, 用于清除内存卡或者U盘里的垃圾文件非常有用

    有一种场景, 手机内存卡空间被用光了,但又不知道哪个文件占用了太大,一个个文件夹去找又太麻烦,所以我开发了个小程序把手机所有文件(包括路径下所有层次子文件夹下的文件)进行一个排序,这样你就可以找出哪个 ...

  8. Java多线程基础学习(二)

    9. 线程安全/共享变量——同步 当多个线程用到同一个变量时,在修改值时存在同时修改的可能性,而此时该变量只能被赋值一次.这就会导致出现“线程安全”问题,这个被多个线程共用的变量称之为“共享变量”. ...

  9. Java多线程基础学习(一)

    1. 创建线程    1.1 通过构造函数:public Thread(Runnable target, String name){}  或:public Thread(Runnable target ...

随机推荐

  1. springboot秒杀课程学习整理1-4

    1)商品模型设计 (应该是先设计商品的model,然后才是数据库表) 模型字段(id,title,price(double),stock(库存),description,sales,imgUrl) 创 ...

  2. oracle数据库自学笔记(持续更新中……)

    以前的项目都是使用mysql数据库开发的,如今进了新的公司,开始接触到了Oracle数据库,而自己以前没有接触过,就自己挤时间来学习一下. 一.关系型数据库的概念 关系型数据理论由E.F.Codd博士 ...

  3. python机器可读数据-json

    导入JSON数据相对简单.下面为打开.加载.导入.输出的操作. import json data = open('data.json').read() data = json.loads(data) ...

  4. grid-layout

    <!-- 创建三个网格布局--> .wrapper { <!--创建一个网格布局 --> display: grid; <!--创建3列 且每列都等距 --> gr ...

  5. DateTime类使用二:常用时间段操作(经典)

    //大家在做报表或查询的时候都会有给用户预设一些可选的日期范围(如上图)                //如本年度销售额.本季度利润.本月新增客户                //C#里内置的Da ...

  6. SpringCloud----熔断机制 -- 断路器hystrix

    参考借鉴:http://www.cnblogs.com/chry/p/7279856.html SpringCloud Netflix实现了断路器库的名字叫Hystrix. 在微服务架构下,通常会有多 ...

  7. win 10 安装 maven安装包

    学习jenkins 的时候,需要用到maven,第一次搞maven,记录下 一.准备工作,下载 jdk7.0以上版本 win10操作系统 maven安装包  下载地址  如下图 二.解压安装包 我的安 ...

  8. HTML5 浏览器支持

    css重置 header, section, footer, aside, nav, main, article, figure { display: block; } 为HTML添加新的元素 < ...

  9. Flex-box入门---flex-grow, flex-shrink, flex-basis

    Block Elements and inline elements(块元素和行内元素) 在进入正题之前,我们先来简单总结一下传统的block元素和inline元素. HTML中的block元素显示在 ...

  10. C语言编译数组中有的加code有的不加,有什么区别

    像uchar code table[]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};和uchar ds1302[]={0xfe,0xfd,0xfb,0xf7,0 ...