1db6da486217daa3418944cf93086a3bae549dad2Yorke Lee/*
2db6da486217daa3418944cf93086a3bae549dad2Yorke Lee * Copyright (C) 2014 The Android Open Source Project
3db6da486217daa3418944cf93086a3bae549dad2Yorke Lee *
4db6da486217daa3418944cf93086a3bae549dad2Yorke Lee * Licensed under the Apache License, Version 2.0 (the "License");
5db6da486217daa3418944cf93086a3bae549dad2Yorke Lee * you may not use this file except in compliance with the License.
6db6da486217daa3418944cf93086a3bae549dad2Yorke Lee * You may obtain a copy of the License at
7db6da486217daa3418944cf93086a3bae549dad2Yorke Lee *
8db6da486217daa3418944cf93086a3bae549dad2Yorke Lee *      http://www.apache.org/licenses/LICENSE-2.0
9db6da486217daa3418944cf93086a3bae549dad2Yorke Lee *
10db6da486217daa3418944cf93086a3bae549dad2Yorke Lee * Unless required by applicable law or agreed to in writing, software
11db6da486217daa3418944cf93086a3bae549dad2Yorke Lee * distributed under the License is distributed on an "AS IS" BASIS,
12db6da486217daa3418944cf93086a3bae549dad2Yorke Lee * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13db6da486217daa3418944cf93086a3bae549dad2Yorke Lee * See the License for the specific language governing permissions and
14db6da486217daa3418944cf93086a3bae549dad2Yorke Lee * limitations under the License.
15db6da486217daa3418944cf93086a3bae549dad2Yorke Lee */
16db6da486217daa3418944cf93086a3bae549dad2Yorke Lee
17db6da486217daa3418944cf93086a3bae549dad2Yorke Leepackage com.android.commands.telecom;
18db6da486217daa3418944cf93086a3bae549dad2Yorke Lee
19db6da486217daa3418944cf93086a3bae549dad2Yorke Leeimport android.content.ComponentName;
20db6da486217daa3418944cf93086a3bae549dad2Yorke Leeimport android.content.Context;
21d3586e174f6d14fd6079e4a2a2b7908b6e45c834Roshan Piusimport android.net.Uri;
226390bd8d698e75232bf42f27e70e9702051e8a8aTony Makimport android.os.IUserManager;
236390bd8d698e75232bf42f27e70e9702051e8a8aTony Makimport android.os.Process;
24db6da486217daa3418944cf93086a3bae549dad2Yorke Leeimport android.os.RemoteException;
25db6da486217daa3418944cf93086a3bae549dad2Yorke Leeimport android.os.ServiceManager;
266390bd8d698e75232bf42f27e70e9702051e8a8aTony Makimport android.os.UserHandle;
27b9381c790eef869ebd904c92bd019886600fc814Yorke Leeimport android.telecom.PhoneAccount;
28db6da486217daa3418944cf93086a3bae549dad2Yorke Leeimport android.telecom.PhoneAccountHandle;
29db6da486217daa3418944cf93086a3bae549dad2Yorke Lee
30db6da486217daa3418944cf93086a3bae549dad2Yorke Leeimport com.android.internal.os.BaseCommand;
31db6da486217daa3418944cf93086a3bae549dad2Yorke Leeimport com.android.internal.telecom.ITelecomService;
32db6da486217daa3418944cf93086a3bae549dad2Yorke Lee
33db6da486217daa3418944cf93086a3bae549dad2Yorke Leeimport java.io.PrintStream;
34db6da486217daa3418944cf93086a3bae549dad2Yorke Lee
35db6da486217daa3418944cf93086a3bae549dad2Yorke Leepublic final class Telecom extends BaseCommand {
36db6da486217daa3418944cf93086a3bae549dad2Yorke Lee
37db6da486217daa3418944cf93086a3bae549dad2Yorke Lee    /**
38db6da486217daa3418944cf93086a3bae549dad2Yorke Lee     * Command-line entry point.
39db6da486217daa3418944cf93086a3bae549dad2Yorke Lee     *
40db6da486217daa3418944cf93086a3bae549dad2Yorke Lee     * @param args The command-line arguments
41db6da486217daa3418944cf93086a3bae549dad2Yorke Lee     */
42db6da486217daa3418944cf93086a3bae549dad2Yorke Lee    public static void main(String[] args) {
43db6da486217daa3418944cf93086a3bae549dad2Yorke Lee      (new Telecom()).run(args);
44db6da486217daa3418944cf93086a3bae549dad2Yorke Lee    }
45db6da486217daa3418944cf93086a3bae549dad2Yorke Lee
46b9381c790eef869ebd904c92bd019886600fc814Yorke Lee    private static final String COMMAND_SET_PHONE_ACCOUNT_ENABLED = "set-phone-account-enabled";
47b9381c790eef869ebd904c92bd019886600fc814Yorke Lee    private static final String COMMAND_SET_PHONE_ACCOUNT_DISABLED = "set-phone-account-disabled";
48b9381c790eef869ebd904c92bd019886600fc814Yorke Lee    private static final String COMMAND_REGISTER_PHONE_ACCOUNT = "register-phone-account";
49d3586e174f6d14fd6079e4a2a2b7908b6e45c834Roshan Pius    private static final String COMMAND_REGISTER_SIM_PHONE_ACCOUNT = "register-sim-phone-account";
50b9381c790eef869ebd904c92bd019886600fc814Yorke Lee    private static final String COMMAND_UNREGISTER_PHONE_ACCOUNT = "unregister-phone-account";
51db6da486217daa3418944cf93086a3bae549dad2Yorke Lee    private static final String COMMAND_SET_DEFAULT_DIALER = "set-default-dialer";
52db6da486217daa3418944cf93086a3bae549dad2Yorke Lee    private static final String COMMAND_GET_DEFAULT_DIALER = "get-default-dialer";
538cd95ee0d0d643d3500fbd0841b658053623902eYorke Lee    private static final String COMMAND_GET_SYSTEM_DIALER = "get-system-dialer";
54db6da486217daa3418944cf93086a3bae549dad2Yorke Lee
55db6da486217daa3418944cf93086a3bae549dad2Yorke Lee    private ComponentName mComponent;
56db6da486217daa3418944cf93086a3bae549dad2Yorke Lee    private String mAccountId;
57db6da486217daa3418944cf93086a3bae549dad2Yorke Lee    private ITelecomService mTelecomService;
586390bd8d698e75232bf42f27e70e9702051e8a8aTony Mak    private IUserManager mUserManager;
59db6da486217daa3418944cf93086a3bae549dad2Yorke Lee
60db6da486217daa3418944cf93086a3bae549dad2Yorke Lee    @Override
61db6da486217daa3418944cf93086a3bae549dad2Yorke Lee    public void onShowUsage(PrintStream out) {
62db6da486217daa3418944cf93086a3bae549dad2Yorke Lee        out.println(
63db6da486217daa3418944cf93086a3bae549dad2Yorke Lee                "usage: telecom [subcommand] [options]\n" +
646390bd8d698e75232bf42f27e70e9702051e8a8aTony Mak                "usage: telecom set-phone-account-enabled <COMPONENT> <ID> <USER_SN>\n" +
656390bd8d698e75232bf42f27e70e9702051e8a8aTony Mak                "usage: telecom set-phone-account-disabled <COMPONENT> <ID> <USER_SN>\n" +
666390bd8d698e75232bf42f27e70e9702051e8a8aTony Mak                "usage: telecom register-phone-account <COMPONENT> <ID> <USER_SN> <LABEL>\n" +
676390bd8d698e75232bf42f27e70e9702051e8a8aTony Mak                "usage: telecom register-sim-phone-account <COMPONENT> <ID> <USER_SN> <LABEL> <ADDRESS>\n" +
686390bd8d698e75232bf42f27e70e9702051e8a8aTony Mak                "usage: telecom unregister-phone-account <COMPONENT> <ID> <USER_SN>\n" +
69db6da486217daa3418944cf93086a3bae549dad2Yorke Lee                "usage: telecom set-default-dialer <PACKAGE>\n" +
708cd95ee0d0d643d3500fbd0841b658053623902eYorke Lee                "usage: telecom get-default-dialer\n" +
718cd95ee0d0d643d3500fbd0841b658053623902eYorke Lee                "usage: telecom get-system-dialer\n" +
72db6da486217daa3418944cf93086a3bae549dad2Yorke Lee                "\n" +
73db6da486217daa3418944cf93086a3bae549dad2Yorke Lee                "telecom set-phone-account-enabled: Enables the given phone account, if it has \n" +
74db6da486217daa3418944cf93086a3bae549dad2Yorke Lee                " already been registered with Telecom.\n" +
75db6da486217daa3418944cf93086a3bae549dad2Yorke Lee                "\n" +
76db6da486217daa3418944cf93086a3bae549dad2Yorke Lee                "telecom set-phone-account-disabled: Disables the given phone account, if it \n" +
77db6da486217daa3418944cf93086a3bae549dad2Yorke Lee                " has already been registered with telecom.\n" +
78db6da486217daa3418944cf93086a3bae549dad2Yorke Lee                "\n" +
79adb53b35cc99e1e98bc4c640d534fa00d44ebe31Yorke Lee                "telecom set-default-dialer: Sets the default dialer to the given component. \n" +
80db6da486217daa3418944cf93086a3bae549dad2Yorke Lee                "\n" +
818cd95ee0d0d643d3500fbd0841b658053623902eYorke Lee                "telecom get-default-dialer: Displays the current default dialer. \n" +
828cd95ee0d0d643d3500fbd0841b658053623902eYorke Lee                "\n" +
838cd95ee0d0d643d3500fbd0841b658053623902eYorke Lee                "telecom get-system-dialer: Displays the current system dialer. \n"
84db6da486217daa3418944cf93086a3bae549dad2Yorke Lee                );
85db6da486217daa3418944cf93086a3bae549dad2Yorke Lee    }
86db6da486217daa3418944cf93086a3bae549dad2Yorke Lee
87db6da486217daa3418944cf93086a3bae549dad2Yorke Lee    @Override
88db6da486217daa3418944cf93086a3bae549dad2Yorke Lee    public void onRun() throws Exception {
89db6da486217daa3418944cf93086a3bae549dad2Yorke Lee        mTelecomService = ITelecomService.Stub.asInterface(
90db6da486217daa3418944cf93086a3bae549dad2Yorke Lee                ServiceManager.getService(Context.TELECOM_SERVICE));
91db6da486217daa3418944cf93086a3bae549dad2Yorke Lee        if (mTelecomService == null) {
92db6da486217daa3418944cf93086a3bae549dad2Yorke Lee            showError("Error: Could not access the Telecom Manager. Is the system running?");
93db6da486217daa3418944cf93086a3bae549dad2Yorke Lee            return;
94db6da486217daa3418944cf93086a3bae549dad2Yorke Lee        }
956390bd8d698e75232bf42f27e70e9702051e8a8aTony Mak        mUserManager = IUserManager.Stub
966390bd8d698e75232bf42f27e70e9702051e8a8aTony Mak                .asInterface(ServiceManager.getService(Context.USER_SERVICE));
976390bd8d698e75232bf42f27e70e9702051e8a8aTony Mak        if (mUserManager == null) {
986390bd8d698e75232bf42f27e70e9702051e8a8aTony Mak            showError("Error: Could not access the User Manager. Is the system running?");
996390bd8d698e75232bf42f27e70e9702051e8a8aTony Mak            return;
1006390bd8d698e75232bf42f27e70e9702051e8a8aTony Mak        }
101db6da486217daa3418944cf93086a3bae549dad2Yorke Lee
102db6da486217daa3418944cf93086a3bae549dad2Yorke Lee        String command = nextArgRequired();
103db6da486217daa3418944cf93086a3bae549dad2Yorke Lee        switch (command) {
104b9381c790eef869ebd904c92bd019886600fc814Yorke Lee            case COMMAND_SET_PHONE_ACCOUNT_ENABLED:
105db6da486217daa3418944cf93086a3bae549dad2Yorke Lee                runSetPhoneAccountEnabled(true);
106db6da486217daa3418944cf93086a3bae549dad2Yorke Lee                break;
107b9381c790eef869ebd904c92bd019886600fc814Yorke Lee            case COMMAND_SET_PHONE_ACCOUNT_DISABLED:
108db6da486217daa3418944cf93086a3bae549dad2Yorke Lee                runSetPhoneAccountEnabled(false);
109db6da486217daa3418944cf93086a3bae549dad2Yorke Lee                break;
110b9381c790eef869ebd904c92bd019886600fc814Yorke Lee            case COMMAND_REGISTER_PHONE_ACCOUNT:
111b9381c790eef869ebd904c92bd019886600fc814Yorke Lee                runRegisterPhoneAccount();
112b9381c790eef869ebd904c92bd019886600fc814Yorke Lee                break;
113d3586e174f6d14fd6079e4a2a2b7908b6e45c834Roshan Pius            case COMMAND_REGISTER_SIM_PHONE_ACCOUNT:
114d3586e174f6d14fd6079e4a2a2b7908b6e45c834Roshan Pius                runRegisterSimPhoneAccount();
115d3586e174f6d14fd6079e4a2a2b7908b6e45c834Roshan Pius                break;
116b9381c790eef869ebd904c92bd019886600fc814Yorke Lee            case COMMAND_UNREGISTER_PHONE_ACCOUNT:
117b9381c790eef869ebd904c92bd019886600fc814Yorke Lee                runUnregisterPhoneAccount();
118b9381c790eef869ebd904c92bd019886600fc814Yorke Lee                break;
119db6da486217daa3418944cf93086a3bae549dad2Yorke Lee            case COMMAND_SET_DEFAULT_DIALER:
120db6da486217daa3418944cf93086a3bae549dad2Yorke Lee                runSetDefaultDialer();
121db6da486217daa3418944cf93086a3bae549dad2Yorke Lee                break;
122db6da486217daa3418944cf93086a3bae549dad2Yorke Lee            case COMMAND_GET_DEFAULT_DIALER:
123db6da486217daa3418944cf93086a3bae549dad2Yorke Lee                runGetDefaultDialer();
124db6da486217daa3418944cf93086a3bae549dad2Yorke Lee                break;
1258cd95ee0d0d643d3500fbd0841b658053623902eYorke Lee            case COMMAND_GET_SYSTEM_DIALER:
1268cd95ee0d0d643d3500fbd0841b658053623902eYorke Lee                runGetSystemDialer();
1278cd95ee0d0d643d3500fbd0841b658053623902eYorke Lee                break;
128db6da486217daa3418944cf93086a3bae549dad2Yorke Lee            default:
129db6da486217daa3418944cf93086a3bae549dad2Yorke Lee                throw new IllegalArgumentException ("unknown command '" + command + "'");
130db6da486217daa3418944cf93086a3bae549dad2Yorke Lee        }
131db6da486217daa3418944cf93086a3bae549dad2Yorke Lee    }
132db6da486217daa3418944cf93086a3bae549dad2Yorke Lee
133db6da486217daa3418944cf93086a3bae549dad2Yorke Lee    private void runSetPhoneAccountEnabled(boolean enabled) throws RemoteException {
134b9381c790eef869ebd904c92bd019886600fc814Yorke Lee        final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
135db6da486217daa3418944cf93086a3bae549dad2Yorke Lee        final boolean success =  mTelecomService.enablePhoneAccount(handle, enabled);
136db6da486217daa3418944cf93086a3bae549dad2Yorke Lee        if (success) {
137db6da486217daa3418944cf93086a3bae549dad2Yorke Lee            System.out.println("Success - " + handle + (enabled ? " enabled." : " disabled."));
138db6da486217daa3418944cf93086a3bae549dad2Yorke Lee        } else {
139db6da486217daa3418944cf93086a3bae549dad2Yorke Lee            System.out.println("Error - is " + handle + " a valid PhoneAccount?");
140db6da486217daa3418944cf93086a3bae549dad2Yorke Lee        }
141db6da486217daa3418944cf93086a3bae549dad2Yorke Lee    }
142db6da486217daa3418944cf93086a3bae549dad2Yorke Lee
143b9381c790eef869ebd904c92bd019886600fc814Yorke Lee    private void runRegisterPhoneAccount() throws RemoteException {
144b9381c790eef869ebd904c92bd019886600fc814Yorke Lee        final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
145b9381c790eef869ebd904c92bd019886600fc814Yorke Lee        final String label = nextArgRequired();
146b9381c790eef869ebd904c92bd019886600fc814Yorke Lee        PhoneAccount account = PhoneAccount.builder(handle, label)
147b9381c790eef869ebd904c92bd019886600fc814Yorke Lee                .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER).build();
148b9381c790eef869ebd904c92bd019886600fc814Yorke Lee        mTelecomService.registerPhoneAccount(account);
149b9381c790eef869ebd904c92bd019886600fc814Yorke Lee        System.out.println("Success - " + handle + " registered.");
150b9381c790eef869ebd904c92bd019886600fc814Yorke Lee    }
151b9381c790eef869ebd904c92bd019886600fc814Yorke Lee
152d3586e174f6d14fd6079e4a2a2b7908b6e45c834Roshan Pius    private void runRegisterSimPhoneAccount() throws RemoteException {
153d3586e174f6d14fd6079e4a2a2b7908b6e45c834Roshan Pius        final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
154d3586e174f6d14fd6079e4a2a2b7908b6e45c834Roshan Pius        final String label = nextArgRequired();
155d3586e174f6d14fd6079e4a2a2b7908b6e45c834Roshan Pius        final String address = nextArgRequired();
156d3586e174f6d14fd6079e4a2a2b7908b6e45c834Roshan Pius        PhoneAccount account = PhoneAccount.builder(
157d3586e174f6d14fd6079e4a2a2b7908b6e45c834Roshan Pius            handle, label)
158d3586e174f6d14fd6079e4a2a2b7908b6e45c834Roshan Pius            .setAddress(Uri.parse(address))
159d3586e174f6d14fd6079e4a2a2b7908b6e45c834Roshan Pius            .setSubscriptionAddress(Uri.parse(address))
160d3586e174f6d14fd6079e4a2a2b7908b6e45c834Roshan Pius            .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
161d3586e174f6d14fd6079e4a2a2b7908b6e45c834Roshan Pius                    PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
162d3586e174f6d14fd6079e4a2a2b7908b6e45c834Roshan Pius            .setShortDescription(label)
163d3586e174f6d14fd6079e4a2a2b7908b6e45c834Roshan Pius            .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
164d3586e174f6d14fd6079e4a2a2b7908b6e45c834Roshan Pius            .addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL)
165d3586e174f6d14fd6079e4a2a2b7908b6e45c834Roshan Pius            .build();
166d3586e174f6d14fd6079e4a2a2b7908b6e45c834Roshan Pius        mTelecomService.registerPhoneAccount(account);
167d3586e174f6d14fd6079e4a2a2b7908b6e45c834Roshan Pius        System.out.println("Success - " + handle + " registered.");
168d3586e174f6d14fd6079e4a2a2b7908b6e45c834Roshan Pius    }
169d3586e174f6d14fd6079e4a2a2b7908b6e45c834Roshan Pius
170b9381c790eef869ebd904c92bd019886600fc814Yorke Lee    private void runUnregisterPhoneAccount() throws RemoteException {
171b9381c790eef869ebd904c92bd019886600fc814Yorke Lee        final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
172b9381c790eef869ebd904c92bd019886600fc814Yorke Lee        mTelecomService.unregisterPhoneAccount(handle);
173b9381c790eef869ebd904c92bd019886600fc814Yorke Lee        System.out.println("Success - " + handle + " unregistered.");
174b9381c790eef869ebd904c92bd019886600fc814Yorke Lee    }
175b9381c790eef869ebd904c92bd019886600fc814Yorke Lee
176db6da486217daa3418944cf93086a3bae549dad2Yorke Lee    private void runSetDefaultDialer() throws RemoteException {
177db6da486217daa3418944cf93086a3bae549dad2Yorke Lee        final String packageName = nextArgRequired();
178db6da486217daa3418944cf93086a3bae549dad2Yorke Lee        final boolean success = mTelecomService.setDefaultDialer(packageName);
179db6da486217daa3418944cf93086a3bae549dad2Yorke Lee        if (success) {
180db6da486217daa3418944cf93086a3bae549dad2Yorke Lee            System.out.println("Success - " + packageName + " set as default dialer.");
181db6da486217daa3418944cf93086a3bae549dad2Yorke Lee        } else {
182db6da486217daa3418944cf93086a3bae549dad2Yorke Lee            System.out.println("Error - " + packageName + " is not an installed Dialer app, \n"
183db6da486217daa3418944cf93086a3bae549dad2Yorke Lee                    + " or is already the default dialer.");
184db6da486217daa3418944cf93086a3bae549dad2Yorke Lee        }
185db6da486217daa3418944cf93086a3bae549dad2Yorke Lee    }
186db6da486217daa3418944cf93086a3bae549dad2Yorke Lee
187db6da486217daa3418944cf93086a3bae549dad2Yorke Lee    private void runGetDefaultDialer() throws RemoteException {
188db6da486217daa3418944cf93086a3bae549dad2Yorke Lee        System.out.println(mTelecomService.getDefaultDialerPackage());
189db6da486217daa3418944cf93086a3bae549dad2Yorke Lee    }
190db6da486217daa3418944cf93086a3bae549dad2Yorke Lee
1918cd95ee0d0d643d3500fbd0841b658053623902eYorke Lee    private void runGetSystemDialer() throws RemoteException {
1928cd95ee0d0d643d3500fbd0841b658053623902eYorke Lee        System.out.println(mTelecomService.getSystemDialerPackage());
1938cd95ee0d0d643d3500fbd0841b658053623902eYorke Lee    }
1948cd95ee0d0d643d3500fbd0841b658053623902eYorke Lee
1956390bd8d698e75232bf42f27e70e9702051e8a8aTony Mak    private PhoneAccountHandle getPhoneAccountHandleFromArgs() throws RemoteException{
196b9381c790eef869ebd904c92bd019886600fc814Yorke Lee        final ComponentName component = parseComponentName(nextArgRequired());
197b9381c790eef869ebd904c92bd019886600fc814Yorke Lee        final String accountId = nextArgRequired();
1986390bd8d698e75232bf42f27e70e9702051e8a8aTony Mak        final String userSnInStr = nextArgRequired();
1996390bd8d698e75232bf42f27e70e9702051e8a8aTony Mak        UserHandle userHandle;
2006390bd8d698e75232bf42f27e70e9702051e8a8aTony Mak        try {
2016390bd8d698e75232bf42f27e70e9702051e8a8aTony Mak            final int userSn = Integer.parseInt(userSnInStr);
2026390bd8d698e75232bf42f27e70e9702051e8a8aTony Mak            userHandle = UserHandle.of(mUserManager.getUserHandle(userSn));
2036390bd8d698e75232bf42f27e70e9702051e8a8aTony Mak        } catch (NumberFormatException ex) {
2046390bd8d698e75232bf42f27e70e9702051e8a8aTony Mak            throw new IllegalArgumentException ("Invalid user serial number " + userSnInStr);
2056390bd8d698e75232bf42f27e70e9702051e8a8aTony Mak        }
2066390bd8d698e75232bf42f27e70e9702051e8a8aTony Mak        return new PhoneAccountHandle(component, accountId, userHandle);
207b9381c790eef869ebd904c92bd019886600fc814Yorke Lee    }
208b9381c790eef869ebd904c92bd019886600fc814Yorke Lee
209db6da486217daa3418944cf93086a3bae549dad2Yorke Lee    private ComponentName parseComponentName(String component) {
210db6da486217daa3418944cf93086a3bae549dad2Yorke Lee        ComponentName cn = ComponentName.unflattenFromString(component);
211db6da486217daa3418944cf93086a3bae549dad2Yorke Lee        if (cn == null) {
212db6da486217daa3418944cf93086a3bae549dad2Yorke Lee            throw new IllegalArgumentException ("Invalid component " + component);
213db6da486217daa3418944cf93086a3bae549dad2Yorke Lee        }
214db6da486217daa3418944cf93086a3bae549dad2Yorke Lee        return cn;
215db6da486217daa3418944cf93086a3bae549dad2Yorke Lee    }
216db6da486217daa3418944cf93086a3bae549dad2Yorke Lee}