zoukankan      html  css  js  c++  java
  • Keyguard分析

        从Android 6.0开始,位于frameworks/bases/packages/Keyguard的Keyguard开始被编译为一个jar包,被SystemUI静态导入,相当于SystemUI的一个界面,这样Keyguard就可以复用SystemUI里关于通知的那一部分代码,这个在Keyuard的Makefile里可以看到

     1 LOCAL_PATH:= $(call my-dir)
     2 17include $(CLEAR_VARS)
     3 18
     4 19LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-subdir-Iaidl-files)
     5 20
     6 21LOCAL_MODULE := Keyguard
     7 22
     8 23LOCAL_CERTIFICATE := platform
     9 24
    10 25LOCAL_JAVA_LIBRARIES := SettingsLib
    11 26
    12 27LOCAL_PRIVILEGED_MODULE := true
    13 28
    14 29LOCAL_PROGUARD_FLAG_FILES := proguard.flags
    15 30
    16 31LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
    17 32
    18 33include $(BUILD_STATIC_JAVA_LIBRARY)
    19 34
    20 35#include $(call all-makefiles-under,$(LOCAL_PATH))

           Keyguard分为两个界面,不用输入密码的一级锁屏界面(SystemUI认为是PhoneStatusBar)与相应源码文件包含Security字样的二级锁屏界面(SystemUI认为是Bouncer)。

           

           一级锁屏界面

           

           二级锁屏界面

           各个部件调用关系是下边这张图

           

           可以看到,Keyguard第一个涉及到的是KeyguardDisplayManager,其由KeyguardViewMediator这个界面中介调用。

           首先,由SystemServer的startSystemUi方法里的StartServiceAsUser连接到SystemUIService。再由SystemUIService(SystemUI/src/com/android/systemui/SystemUIService.java)里的onCreate函数调用(就这一个有用的函数)startServicesIfNeeded方法,开始SystemUI的初始化

    1     static final void startSystemUi(Context context) {
    2         Intent intent = new Intent();
    3         intent.setComponent(new ComponentName("com.android.systemui",
    4                     "com.android.systemui.SystemUIService"));
    5         intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
    6         //Slog.d(TAG, "Starting service: " + intent);
    7         context.startServiceAsUser(intent, UserHandle.SYSTEM);
    8     }
    1 public class SystemUIService extends Service {
    2 
    3     @Override
    4     public void onCreate() {
    5         super.onCreate();
    6         ((SystemUIApplication) getApplication()).startServicesIfNeeded();
    7     }

       

           KeyguardViewMediator是SystemUIApplication名为SERVICES数组里的一员,这个数组的东西都是SystemUI要load的Service的class,这个框架流程在start的时候,调用这些类的start方法,并在bootcompleted的时候调用这些类的onbootcompleted方法。

     1  /**
     2 44     * The classes of the stuff to start.
     3 45     */
     4 46    private final Class<?>[] SERVICES = new Class[] {
     5 47            com.android.systemui.tuner.TunerService.class,
     6 48            com.android.systemui.keyguard.KeyguardViewMediator.class,
     7 49            com.android.systemui.recents.Recents.class,
     8 50            com.android.systemui.volume.VolumeUI.class,
     9 51            Divider.class,
    10 52            com.android.systemui.statusbar.SystemBars.class,
    11 53            com.android.systemui.usb.StorageNotification.class,
    12 54            com.android.systemui.power.PowerUI.class,
    13 55            com.android.systemui.media.RingtonePlayer.class,
    14 56            com.android.systemui.keyboard.KeyboardUI.class,
    15 57            com.android.systemui.tv.pip.PipUI.class,
    16 58            com.android.systemui.shortcut.ShortcutKeyDispatcher.class,
    17 59            com.android.systemui.VendorServices.class
    18 60    };

        可以看到,在SystemUIApplication这个类的startServicesIfNeeded里,会依次调用SERVICES里的start函数,这里会先调用com.android.systemui.keyguard.KeyguardViewMediator的start方法

     1 final int N = services.length;
     2 156        for (int i=0; i<N; i++) {
     3 157            Class<?> cl = services[i];
     4 158            if (DEBUG) Log.d(TAG, "loading: " + cl);
     5 159            try {
     6 160                Object newService = SystemUIFactory.getInstance().createInstance(cl);
     7 161                mServices[i] = (SystemUI) ((newService == null) ? cl.newInstance() : newService);
     8 162            } catch (IllegalAccessException ex) {
     9 163                throw new RuntimeException(ex);
    10 164            } catch (InstantiationException ex) {
    11 165                throw new RuntimeException(ex);
    12 166            }
    13 167
    14 168            mServices[i].mContext = this;
    15 169            mServices[i].mComponents = mComponents;
    16 170            if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
    17 171            mServices[i].start();
    18 172
    19 173            if (mBootCompleted) {
    20 174                mServices[i].onBootCompleted();
    21 175            }
    22 176        }
    23 177        mServicesStarted = true;

        

          下面看下KeyguardViewMediator及其start方法

           这个类位于SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java比较长。第三方客户端也可以通过调用KeyguardManager这个类来获取和修改锁屏的信息、状态,这个类是锁屏操作的binder server基础类。

           KeyguardViewMediator是抽象类SystemUI的一个具体实现子类,SystemUI这个类的主要方法是putComponent和getComponent,保存和获取相应类对应的实际组件的映射。还用mContext和mComponents保存相应的SystemUIApplication实例和其中名为component的hashmap。

           KyeguardViewMediator总体负责所有的锁屏状态,并根据状态来决定调用哪些组件。

           KeyguardViewMediator的start方法很简单,初始化锁屏状态,把KeyguardViewMediator的class和KeyguardViewMediator建立映射。

    1  @Override
    2 699    public void start() {
    3 700        synchronized (this) {
    4 701            setupLocked();
    5 702        }
    6 703        putComponent(KeyguardViewMediator.class, this);
    7 704    }

            初始化的过程在setupLocked方法里完成,首先获取系统的PowerManagerService,WindowManagerService,TrustManagerService并初始化一把partial wakelock锁

    1            mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
    2 635        mWM = WindowManagerGlobal.getWindowManagerService();
    3 636        mTrustManager = (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
    4 637
    5 638        mShowKeyguardWakeLock = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "show keyguard");
    6 639        mShowKeyguardWakeLock.setReferenceCounted(false);

              随后注册DELAYED_KEYGUARD_ACTION和DELAYED_LOCK_PROFILE_ACTION这两个Intent的broadcastreceiver

    1 641        mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(DELAYED_KEYGUARD_ACTION));
    2 642        mContext.registerReceiver(
    3 643                mBroadcastReceiver, new IntentFilter(DELAYED_LOCK_PROFILE_ACTION));

               然后创建Keyguard包里的KeyguardDisplayManager和KeyguardUpdateMonitor,还有锁屏模式工具类,获取AlarmManagerService,给KeyguardUpdateMonitor设置当前的用户。

    1 645        mKeyguardDisplayManager = new KeyguardDisplayManager(mContext);
    2 646
    3 647        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
    4 648
    5 649        mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
    6 650
    7 651        mLockPatternUtils = new LockPatternUtils(mContext);
    8 652        KeyguardUpdateMonitor.setCurrentUser(ActivityManager.getCurrentUser());

                然后设置锁屏状态的变量并调用锁屏状态改变回调函数表,通知TrustManager

    1 654        // Assume keyguard is showing (unless it's disabled) until we know for sure...
    2 655        setShowingLocked(!shouldWaitForProvisioning() && !mLockPatternUtils.isLockScreenDisabled(
    3 656                KeyguardUpdateMonitor.getCurrentUser()));
    4 657        updateInputRestrictedLocked();
    5 658        mTrustManager.reportKeyguardShowingChanged();

                然后设置视图显示,通过SystemUIFactory获取StatusBarKeyguardViewManager,并把视图中介回调(mViewMediatorCallback)和锁屏模式工具(mLockPatternUtils)传入。

    1 660        mStatusBarKeyguardViewManager =
    2 661                SystemUIFactory.getInstance().createStatusBarKeyguardViewManager(mContext,
    3 662                        mViewMediatorCallback, mLockPatternUtils);

                SystemUIFactory里的code如下

    1 84    public StatusBarKeyguardViewManager createStatusBarKeyguardViewManager(Context context,
    2 85            ViewMediatorCallback viewMediatorCallback, LockPatternUtils lockPatternUtils) {
    3 86        return new StatusBarKeyguardViewManager(context, viewMediatorCallback, lockPatternUtils);
    4 87    }

                StatusBarKeyguardViewManager是SystemUI中的一个状态栏组件,是锁屏的视图。

                最后设置锁屏和解锁的声音的文件和音量,获取设备交互状态,加载锁屏隐藏的动画 com.android.internal.R.anim.lock_screen_behind_enter 。

    
    
      663        final ContentResolver cr = mContext.getContentResolver();
      664
      665        mDeviceInteractive = mPM.isInteractive();

    1
    667 mLockSounds = new SoundPool(1, AudioManager.STREAM_SYSTEM, 0); 2 668 String soundPath = Settings.Global.getString(cr, Settings.Global.LOCK_SOUND); 3 669 if (soundPath != null) { 4 670 mLockSoundId = mLockSounds.load(soundPath, 1); 5 671 } 6 672 if (soundPath == null || mLockSoundId == 0) { 7 673 Log.w(TAG, "failed to load lock sound from " + soundPath); 8 674 } 9 675 soundPath = Settings.Global.getString(cr, Settings.Global.UNLOCK_SOUND); 10 676 if (soundPath != null) { 11 677 mUnlockSoundId = mLockSounds.load(soundPath, 1); 12 678 } 13 679 if (soundPath == null || mUnlockSoundId == 0) { 14 680 Log.w(TAG, "failed to load unlock sound from " + soundPath); 15 681 } 16 682 soundPath = Settings.Global.getString(cr, Settings.Global.TRUSTED_SOUND); 17 683 if (soundPath != null) { 18 684 mTrustedSoundId = mLockSounds.load(soundPath, 1); 19 685 } 20 686 if (soundPath == null || mTrustedSoundId == 0) { 21 687 Log.w(TAG, "failed to load trusted sound from " + soundPath); 22 688 } 23 689 24 690 int lockSoundDefaultAttenuation = mContext.getResources().getInteger( 25 691 com.android.internal.R.integer.config_lockSoundVolumeDb); 26 692 mLockSoundVolume = (float)Math.pow(10, (float)lockSoundDefaultAttenuation/20); 27 693 28 694 mHideAnimation = AnimationUtils.loadAnimation(mContext, 29 695 com.android.internal.R.anim.lock_screen_behind_enter); 30 696 }

              然后SystemUIApplication会调用  mServices[i].onBootCompleted 方法,会在KeyguardViemMediator的start方法后调用,来发出Intent ACTION_BOOT_COMPLETED,通知其他组件锁屏初始化完成

              上边是锁屏的初始化过程,然后就是锁屏的加载过程。锁屏界面的加载有两个地方,第一个是第一次开机的时候;第二个是在灭屏后,这个时候会预加载锁屏界面加速亮屏显示。

               第一次开机时,,

               在按住Power键灭屏的时候,流程如下

               可以看到,KeyguardViewMediator里有两个回调函数被涉及

               第一个是onStartedGoingToSleep。这个方法里做锁屏的一些预处理,并发出锁屏通知给KeyguardUpdateMonitor(这里的状态太多了)

     1 723    /**
     2 724     * Called to let us know the screen was turned off.
     3 725     * @param why either {@link android.view.WindowManagerPolicy#OFF_BECAUSE_OF_USER} or
     4 726     *   {@link android.view.WindowManagerPolicy#OFF_BECAUSE_OF_TIMEOUT}.
     5 727     */
     6 728    public void onStartedGoingToSleep(int why) {
     7 729        if (DEBUG) Log.d(TAG, "onStartedGoingToSleep(" + why + ")");
     8 730        synchronized (this) {
     9 731            mDeviceInteractive = false;
    10 732            mGoingToSleep = true;
    11 733
    12 734            // Lock immediately based on setting if secure (user has a pin/pattern/password).
    13 735            // This also "locks" the device when not secure to provide easy access to the
    14 736            // camera while preventing unwanted input.
    15 737            int currentUser = KeyguardUpdateMonitor.getCurrentUser();
    16 738            final boolean lockImmediately =
    17 739                    mLockPatternUtils.getPowerButtonInstantlyLocks(currentUser)
    18 740                            || !mLockPatternUtils.isSecure(currentUser);
    19 741            long timeout = getLockTimeout(KeyguardUpdateMonitor.getCurrentUser());
    20 742            mLockLater = false;
    21 743            if (mExitSecureCallback != null) {
    22 744                if (DEBUG) Log.d(TAG, "pending exit secure callback cancelled");
    23 745                try {
    24 746                    mExitSecureCallback.onKeyguardExitResult(false);
    25 747                } catch (RemoteException e) {
    26 748                    Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
    27 749                }
    28 750                mExitSecureCallback = null;
    29 751                if (!mExternallyEnabled) {
    30 752                    hideLocked();
    31 753                }
    32 754            } else if (mShowing) {
    33 755                mPendingReset = true;
    34 756            } else if ((why == WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT && timeout > 0)
    35 757                    || (why == WindowManagerPolicy.OFF_BECAUSE_OF_USER && !lockImmediately)) {
    36 758                doKeyguardLaterLocked(timeout);
    37 759                mLockLater = true;
    38 760            } else if (!mLockPatternUtils.isLockScreenDisabled(currentUser)) {
    39 761                mPendingLock = true;
    40 762            }
    41 763
    42 764            if (mPendingLock) {
    43 765                playSounds(true);
    44 766            }
    45 767        }
    46 768        KeyguardUpdateMonitor.getInstance(mContext).dispatchStartedGoingToSleep(why);
    47 769        notifyStartedGoingToSleep();
    48 770    }

               第二个是onFinishedGoingToSleep,可以看到核心方法是doKeyguardLocked和doKeyguardForChildProfilesLocked

     1 772    public void onFinishedGoingToSleep(int why, boolean cameraGestureTriggered) {
     2 773        if (DEBUG) Log.d(TAG, "onFinishedGoingToSleep(" + why + ")");
     3 774        synchronized (this) {
     4 775            mDeviceInteractive = false;
     5 776            mGoingToSleep = false;
     6 777
     7 778            resetKeyguardDonePendingLocked();
     8 779            mHideAnimationRun = false;
     9 780
    10 781            notifyFinishedGoingToSleep();
    11 782
    12 783            if (cameraGestureTriggered) {
    13 784                Log.i(TAG, "Camera gesture was triggered, preventing Keyguard locking.");
    14 785
    15 786                // Just to make sure, make sure the device is awake.
    16 787                mContext.getSystemService(PowerManager.class).wakeUp(SystemClock.uptimeMillis(),
    17 788                        "com.android.systemui:CAMERA_GESTURE_PREVENT_LOCK");
    18 789                mPendingLock = false;
    19 790                mPendingReset = false;
    20 791            }
    21 792
    22 793            if (mPendingReset) {
    23 794                resetStateLocked();
    24 795                mPendingReset = false;
    25 796            }
    26 797
    27 798            if (mPendingLock) {
    28 799                doKeyguardLocked(null);
    29 800                mPendingLock = false;
    30 801            }
    31 802
    32 803            // We do not have timeout and power button instant lock setting for profile lock.
    33 804            // So we use the personal setting if there is any. But if there is no device
    34 805            // we need to make sure we lock it immediately when the screen is off.
    35 806            if (!mLockLater && !cameraGestureTriggered) {
    36 807                doKeyguardForChildProfilesLocked();
    37 808            }
    38 809
    39 810        }
    40 811        KeyguardUpdateMonitor.getInstance(mContext).dispatchFinishedGoingToSleep(why);
    41 812    }

             doKeyguardLocked 会先判断要不要锁屏,如果需要,则调用方法showLocked

     1 1192    /**
     2 1193     * Enable the keyguard if the settings are appropriate.
     3 1194     */
     4 1195    private void doKeyguardLocked(Bundle options) {
     5 1196        // if another app is disabling us, don't show
     6 1197        if (!mExternallyEnabled) {
     7 1198            if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
     8 1199
     9 1200            // note: we *should* set mNeedToReshowWhenReenabled=true here, but that makes
    10 1201            // for an occasional ugly flicker in this situation:
    11 1202            // 1) receive a call with the screen on (no keyguard) or make a call
    12 1203            // 2) screen times out
    13 1204            // 3) user hits key to turn screen back on
    14 1205            // instead, we reenable the keyguard when we know the screen is off and the call
    15 1206            // ends (see the broadcast receiver below)
    16 1207            // TODO: clean this up when we have better support at the window manager level
    17 1208            // for apps that wish to be on top of the keyguard
    18 1209            return;
    19 1210        }
    20 1211
    21 1212        // if the keyguard is already showing, don't bother
    22 1213        if (mStatusBarKeyguardViewManager.isShowing()) {
    23 1214            if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing");
    24 1215            resetStateLocked();
    25 1216            return;
    26 1217        }
    27 1218
    28 1219        // In split system user mode, we never unlock system user.
    29 1220        if (!mustNotUnlockCurrentUser()
    30 1221                || !mUpdateMonitor.isDeviceProvisioned()) {
    31 1222
    32 1223            // if the setup wizard hasn't run yet, don't show
    33 1224            final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim", false);
    34 1225            final boolean absent = SubscriptionManager.isValidSubscriptionId(
    35 1226                    mUpdateMonitor.getNextSubIdForState(IccCardConstants.State.ABSENT));
    36 1227            final boolean disabled = SubscriptionManager.isValidSubscriptionId(
    37 1228                    mUpdateMonitor.getNextSubIdForState(IccCardConstants.State.PERM_DISABLED));
    38 1229            final boolean lockedOrMissing = mUpdateMonitor.isSimPinSecure()
    39 1230                    || ((absent || disabled) && requireSim);
    40 1231
    41 1232            if (!lockedOrMissing && shouldWaitForProvisioning()) {
    42 1233                if (DEBUG) Log.d(TAG, "doKeyguard: not showing because device isn't provisioned"
    43 1234                        + " and the sim is not locked or missing");
    44 1235                return;
    45 1236            }
    46 1237
    47 1238            if (mLockPatternUtils.isLockScreenDisabled(KeyguardUpdateMonitor.getCurrentUser())
    48 1239                    && !lockedOrMissing) {
    49 1240                if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off");
    50 1241                return;
    51 1242            }
    52 1243
    53 1244            if (mLockPatternUtils.checkVoldPassword(KeyguardUpdateMonitor.getCurrentUser())) {
    54 1245                if (DEBUG) Log.d(TAG, "Not showing lock screen since just decrypted");
    55 1246                // Without this, settings is not enabled until the lock screen first appears
    56 1247                setShowingLocked(false);
    57 1248                hideLocked();
    58 1249                mUpdateMonitor.reportSuccessfulStrongAuthUnlockAttempt();
    59 1250                return;
    60 1251            }
    61 1252        }
    62 1253
    63 1254        if (DEBUG) Log.d(TAG, "doKeyguard: showing the lock screen");
    64 1255        showLocked(options);
    65 1256    }

               showLocked方法会发送SHOW消息

     1 1332    /**
     2 1333     * Send message to keyguard telling it to show itself
     3 1334     * @see #handleShow
     4 1335     */
     5 1336    private void showLocked(Bundle options) {
     6 1337        Trace.beginSection("KeyguardViewMediator#showLocked aqcuiring mShowKeyguardWakeLock");
     7 1338        if (DEBUG) Log.d(TAG, "showLocked");
     8 1339        // ensure we stay awake until we are finished displaying the keyguard
     9 1340        mShowKeyguardWakeLock.acquire();
    10 1341        Message msg = mHandler.obtainMessage(SHOW, options);
    11 1342        mHandler.sendMessage(msg);
    12 1343        Trace.endSection();
    13 1344    }

               handleShow方法就会被调用,显示mStatusBarKeyguardViewManagermKeyguardDisplayManager的show方法。

     1 1625    /**
     2 1626     * Handle message sent by {@link #showLocked}.
     3 1627     * @see #SHOW
     4 1628     */
     5 1629    private void handleShow(Bundle options) {
     6 1630        Trace.beginSection("KeyguardViewMediator#handleShow");
     7 1631        final int currentUser = KeyguardUpdateMonitor.getCurrentUser();
     8 1632        if (mLockPatternUtils.isSecure(currentUser)) {
     9 1633            mLockPatternUtils.getDevicePolicyManager().reportKeyguardSecured(currentUser);
    10 1634        }
    11 1635        synchronized (KeyguardViewMediator.this) {
    12 1636            if (!mSystemReady) {
    13 1637                if (DEBUG) Log.d(TAG, "ignoring handleShow because system is not ready.");
    14 1638                return;
    15 1639            } else {
    16 1640                if (DEBUG) Log.d(TAG, "handleShow");
    17 1641            }
    18 1642
    19 1643            setShowingLocked(true);
    20 1644            mStatusBarKeyguardViewManager.show(options);
    21 1645            mHiding = false;
    22 1646            mWakeAndUnlocking = false;
    23 1647            resetKeyguardDonePendingLocked();
    24 1648            mHideAnimationRun = false;
    25 1649            updateActivityLockScreenState();
    26 1650            adjustStatusBarLocked();
    27 1651            userActivity();
    28 1652
    29 1653            mShowKeyguardWakeLock.release();
    30 1654        }
    31 1655        mKeyguardDisplayManager.show();
    32 1656        Trace.endSection();
    33 1657    }

               

               还有一种情况是超时灭屏,与上边的按住Power键灭屏流程基本一样

    基本流程分析完了,下面看看Keyguard里的具体每个类

    先看KeyguardDisplayManager这个类,这个类是控制手机远程显示的。如果手机远程连接上了电视这样的设备,就先一个一个KeyguardPresentation对话框,是个时钟

     1     protected void updateDisplays(boolean showing) {
     2         if (showing) {
     3             MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(
     4                     MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY);
     5             boolean useDisplay = route != null
     6                     && route.getPlaybackType() == MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE;
     7             Display presentationDisplay = useDisplay ? route.getPresentationDisplay() : null;
     8 
     9             if (mPresentation != null && mPresentation.getDisplay() != presentationDisplay) {
    10                 if (DEBUG) Slog.v(TAG, "Display gone: " + mPresentation.getDisplay());
    11                 mPresentation.dismiss();
    12                 mPresentation = null;
    13             }
    14 
    15             if (mPresentation == null && presentationDisplay != null) {
    16                 if (DEBUG) Slog.i(TAG, "Keyguard enabled on display: " + presentationDisplay);
    17                 mPresentation = new KeyguardPresentation(mContext, presentationDisplay,
    18                         R.style.keyguard_presentation_theme);
    19                 mPresentation.setOnDismissListener(mOnDismissListener);
    20                 try {
    21                     mPresentation.show();
    22                 } catch (WindowManager.InvalidDisplayException ex) {
    23                     Slog.w(TAG, "Invalid display:", ex);
    24                     mPresentation = null;
    25                 }
    26             }
    27         } else {
    28             if (mPresentation != null) {
    29                 mPresentation.dismiss();
    30                 mPresentation = null;
    31             }
    32         }
    33     }
  • 相关阅读:
    【Codechef】Chef and Bike(二维多项式插值)
    USACO 完结的一些感想
    USACO 6.5 Checker Challenge
    USACO 6.5 The Clocks
    USACO 6.5 Betsy's Tour (插头dp)
    USACO 6.5 Closed Fences
    USACO 6.4 Electric Fences
    USACO 6.5 All Latin Squares
    USACO 6.4 The Primes
    USACO 6.4 Wisconsin Squares
  • 原文地址:https://www.cnblogs.com/cascle/p/7053688.html
Copyright © 2011-2022 走看看