Search in sources :

Example 1 with Downloader

use of de.mkrtchyan.utils.Downloader in project Rashr by DsLNeXuS.

the class RecoverySystemFragment method onCreateView.

@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    mActivity = (RashrActivity) getActivity();
    final ScrollView root = (ScrollView) inflater.inflate(R.layout.fragment_recovery_system, container, false);
    mContext = root.getContext();
    final AppCompatTextView tvTitle = (AppCompatTextView) root.findViewById(R.id.tvSysName);
    tvTitle.setText(mTitle.toUpperCase());
    final AppCompatTextView tvDesc = (AppCompatTextView) root.findViewById(R.id.tvRecSysDesc);
    tvDesc.setText(mDesc);
    final AppCompatSpinner spVersions = (AppCompatSpinner) root.findViewById(R.id.spVersions);
    ArrayList<String> formatedVersions = new ArrayList<>();
    for (String versionLinks : mVersions) {
        formatedVersions.add(formatName(versionLinks, mTitle));
    }
    final ArrayAdapter<String> adapter = new ArrayAdapter<>(root.getContext(), android.R.layout.simple_list_item_1, formatedVersions);
    spVersions.setAdapter(adapter);
    spVersions.setSelection(0);
    final AppCompatTextView tvDev = (AppCompatTextView) root.findViewById(R.id.tvDevName);
    tvDev.setText(mDev);
    final AppCompatImageView imLogo = (AppCompatImageView) root.findViewById(R.id.ivRecLogo);
    if (mLogo == 0) {
        root.removeView(imLogo);
    } else {
        imLogo.setImageResource(mLogo);
    }
    final AppCompatButton bFlash = (AppCompatButton) root.findViewById(R.id.bFlashRecovery);
    bFlash.setOnClickListener(new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            flashSupportedRecovery(mTitle, mVersions.get(spVersions.getSelectedItemPosition()));
        }
    });
    final LinearLayout ScreenshotLayout = (LinearLayout) root.findViewById(R.id.ScreenshotLayout);
    if (mScreenshotURL == null) {
        ((ViewGroup) ScreenshotLayout.getParent()).removeView(ScreenshotLayout);
    } else {
        try {
            Downloader jsonDownloader = new Downloader(new URL(mScreenshotURL + "/getScreenshots.php"), new File(mContext.getExternalCacheDir(), "screenhots.json"));
            jsonDownloader.setOverrideFile(true);
            jsonDownloader.setOnDownloadListener(new Downloader.OnDownloadListener() {

                @Override
                public void onSuccess(File file) {
                    try {
                        JSONArray arr = new JSONArray(Common.fileContent(file));
                        for (int i = 0; i < arr.length(); i++) {
                            final String name = arr.get(i).toString();
                            if (name.equals(".") || name.equals("..") || name.equals("getScreenshots.php"))
                                continue;
                            Downloader imageDownloader = new Downloader(new URL(mScreenshotURL + "/" + name), new File(file.getParentFile(), name));
                            //Do not redownload predownloaded images
                            imageDownloader.setOverrideFile(false);
                            imageDownloader.setOnDownloadListener(new Downloader.OnDownloadListener() {

                                @Override
                                public void onSuccess(File file) {
                                    AppCompatImageView iv = (AppCompatImageView) inflater.inflate(R.layout.recovery_screenshot, null);
                                    try {
                                        final BitmapFactory.Options options = new BitmapFactory.Options();
                                        options.inSampleSize = 8;
                                        Bitmap screenshot = BitmapFactory.decodeFile(file.toString());
                                        iv.setImageBitmap(screenshot);
                                        ScreenshotLayout.addView(iv);
                                    } catch (OutOfMemoryError e) {
                                        App.ERRORS.add("Screenshot " + file.toString() + " could not be decoded " + e.toString());
                                    }
                                }

                                @Override
                                public void onFail(Exception e) {
                                }
                            });
                            imageDownloader.download();
                        }
                    } catch (JSONException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        onFail(e);
                    }
                }

                @Override
                public void onFail(Exception e) {
                    e.printStackTrace();
                }
            });
            jsonDownloader.download();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }
    return root;
}
Also used : MalformedURLException(java.net.MalformedURLException) ArrayList(java.util.ArrayList) Downloader(de.mkrtchyan.utils.Downloader) URL(java.net.URL) Bitmap(android.graphics.Bitmap) BitmapFactory(android.graphics.BitmapFactory) ViewGroup(android.view.ViewGroup) AppCompatTextView(android.support.v7.widget.AppCompatTextView) JSONArray(org.json.JSONArray) JSONException(org.json.JSONException) IOException(java.io.IOException) AppCompatImageView(android.support.v7.widget.AppCompatImageView) View(android.view.View) AppCompatImageView(android.support.v7.widget.AppCompatImageView) AppCompatTextView(android.support.v7.widget.AppCompatTextView) ScrollView(android.widget.ScrollView) AppCompatSpinner(android.support.v7.widget.AppCompatSpinner) JSONException(org.json.JSONException) MalformedURLException(java.net.MalformedURLException) IOException(java.io.IOException) AppCompatButton(android.support.v7.widget.AppCompatButton) ScrollView(android.widget.ScrollView) File(java.io.File) ArrayAdapter(android.widget.ArrayAdapter) LinearLayout(android.widget.LinearLayout)

Example 2 with Downloader

use of de.mkrtchyan.utils.Downloader in project Rashr by DsLNeXuS.

the class RecoverySystemFragment method flashSupportedRecovery.

/**
 * Flash a Recovery provided by Rashr, like ClockworkMod, TWRP, PhilZ, CM, Stock
 *
 * @param system  String containing the Recovery-System type for example:
 *                clockwork, cm, twrp, philz, stock....
 * @param fileUrl File that will be flashed
 */
public void flashSupportedRecovery(final String system, String fileUrl) {
    /*
         * If there files be needed to flash download it and listing device specified
         * recovery file for example recovery-clockwork-touch-6.0.3.1-grouper.img
         * (read out from RECOVERY_SUMS)
         */
    if (system.equals(Device.REC_SYS_XZDUAL)) {
        AlertDialog.Builder alert = new AlertDialog.Builder(getContext());
        alert.setTitle(R.string.warning);
        if (App.Device.isXZDualInstalled()) {
            alert.setMessage(R.string.xzdual_uninstall_alert);
            alert.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int which) {
                    AlertDialog.Builder abuilder = new AlertDialog.Builder(mContext);
                    abuilder.setTitle(R.string.info);
                    abuilder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {

                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                        }
                    });
                    try {
                        FlashUtil.uninstallXZDual();
                        abuilder.setMessage(R.string.xzdual_uninstall_successfull);
                    } catch (FailedExecuteCommand failedExecuteCommand) {
                        abuilder.setMessage(getString(R.string.xzdual_uninstall_failed) + "\n" + failedExecuteCommand.toString());
                        failedExecuteCommand.printStackTrace();
                        App.ERRORS.add(failedExecuteCommand.toString() + " Error uninstalling XZDual");
                    }
                    abuilder.show();
                }
            });
            alert.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {

                @Override
                public void onClick(DialogInterface dialog, int which) {
                }
            });
            return;
        }
    }
    String fileName = "";
    if (system.equals(Device.REC_SYS_TWRP)) {
        fileName = fileUrl.substring(fileUrl.lastIndexOf("/") + 1);
    }
    final File recovery = new File(mImagePath, fileName);
    if (!recovery.exists()) {
        try {
            URL url = new URL(fileUrl);
            final Downloader downloader = new Downloader(url, recovery);
            final DownloadDialog RecoveryDownloader = new DownloadDialog(mContext, downloader);
            if (system.equals(Device.REC_SYS_TWRP)) {
                downloader.setReferrer(fileUrl);
            }
            RecoveryDownloader.setOnDownloadListener(new DownloadDialog.OnDownloadListener() {

                @Override
                public void onSuccess(File file) {
                    if (system.equals(Device.REC_SYS_XZDUAL)) {
                        FlashUtil flasher = new FlashUtil(getActivity(), file, FlashUtil.JOB_INSTALL_XZDUAL);
                        flasher.execute();
                        mActivity.onBackPressed();
                    } else {
                        flashRecovery(file);
                    }
                }

                @Override
                public void onFail(Exception e) {
                    if (e != null) {
                        App.ERRORS.add(e.toString());
                        View v = getView();
                        if (v == null)
                            return;
                        Snackbar.make(v, e.getMessage(), Snackbar.LENGTH_SHORT).show();
                    }
                    RecoveryDownloader.retry();
                }
            });
            RecoveryDownloader.setAskBeforeDownload(true);
            downloader.setChecksumUrl(fileUrl + ".md5");
            RecoveryDownloader.ask();
        } catch (MalformedURLException ignored) {
        }
    } else {
        flashRecovery(recovery);
    }
}
Also used : AlertDialog(android.support.v7.app.AlertDialog) FailedExecuteCommand(org.sufficientlysecure.rootcommands.util.FailedExecuteCommand) FlashUtil(de.mkrtchyan.recoverytools.FlashUtil) MalformedURLException(java.net.MalformedURLException) DialogInterface(android.content.DialogInterface) Downloader(de.mkrtchyan.utils.Downloader) DownloadDialog(de.mkrtchyan.utils.DownloadDialog) BindView(butterknife.BindView) View(android.view.View) AppCompatImageView(android.support.v7.widget.AppCompatImageView) AppCompatTextView(android.support.v7.widget.AppCompatTextView) ScrollView(android.widget.ScrollView) URL(java.net.URL) JSONException(org.json.JSONException) MalformedURLException(java.net.MalformedURLException) IOException(java.io.IOException) File(java.io.File)

Example 3 with Downloader

use of de.mkrtchyan.utils.Downloader in project Rashr by DsLNeXuS.

the class RecoverySystemFragment method onCreateView.

@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container, Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    mActivity = (RashrActivity) getActivity();
    final ScrollView root = (ScrollView) inflater.inflate(R.layout.fragment_recovery_system, container, false);
    ButterKnife.bind(this, root);
    mContext = root.getContext();
    tvTitle.setText(mTitle.toUpperCase());
    tvDesc.setText(mDesc);
    ArrayList<String> formatedVersions = new ArrayList<>();
    for (String versionLinks : mVersions) {
        formatedVersions.add(formatName(versionLinks, mTitle));
    }
    final ArrayAdapter<String> adapter = new ArrayAdapter<>(root.getContext(), android.R.layout.simple_list_item_1, formatedVersions);
    spVersions.setAdapter(adapter);
    spVersions.setSelection(0);
    tvDev.setText(mDev);
    if (mLogo == 0) {
        imLogo.setVisibility(View.GONE);
    } else {
        imLogo.setImageResource(mLogo);
    }
    bFlash.setOnClickListener(new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            flashSupportedRecovery(mTitle, mVersions.get(spVersions.getSelectedItemPosition()));
        }
    });
    if (mScreenshotURL == null) {
        ScreenshotLayout.setVisibility(View.GONE);
    } else {
        try {
            Downloader links = new Downloader(new URL(mScreenshotURL), new File(mContext.getExternalCacheDir(), "screenshots.json"));
            links.setOverrideFile(true);
            links.setOnDownloadListener(new Downloader.OnDownloadListener() {

                @Override
                public void onSuccess(File file) {
                    try {
                        String res = Common.fileContent(file);
                        JSONArray arr = new JSONArray(res);
                        for (int i = 0; i < arr.length(); i++) {
                            final String name = arr.get(i).toString();
                            // Skip file that lists content of url
                            if (name.equals("getScreenshots.php"))
                                continue;
                            if (name.equals("index.php"))
                                continue;
                            // All others are filenames of screenshots
                            AppCompatImageView iv = (AppCompatImageView) inflater.inflate(R.layout.recovery_screenshot, ScreenshotLayout, false);
                            ScreenshotLayout.addView(iv);
                            Picasso picasso = Picasso.with(mContext);
                            picasso.setLoggingEnabled(BuildConfig.DEBUG);
                            picasso.setIndicatorsEnabled(BuildConfig.DEBUG);
                            picasso.load(mScreenshotURL + "/" + name).placeholder(R.drawable.ic_launcher_web).into(iv);
                        }
                    } catch (JSONException | IOException e) {
                        onFail(e);
                    }
                }

                @Override
                public void onFail(Exception e) {
                    e.printStackTrace();
                }
            });
            links.download();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }
    return root;
}
Also used : MalformedURLException(java.net.MalformedURLException) ArrayList(java.util.ArrayList) JSONArray(org.json.JSONArray) Downloader(de.mkrtchyan.utils.Downloader) BindView(butterknife.BindView) View(android.view.View) AppCompatImageView(android.support.v7.widget.AppCompatImageView) AppCompatTextView(android.support.v7.widget.AppCompatTextView) ScrollView(android.widget.ScrollView) AppCompatImageView(android.support.v7.widget.AppCompatImageView) URL(java.net.URL) JSONException(org.json.JSONException) MalformedURLException(java.net.MalformedURLException) IOException(java.io.IOException) ScrollView(android.widget.ScrollView) Picasso(com.squareup.picasso.Picasso) File(java.io.File) ArrayAdapter(android.widget.ArrayAdapter)

Example 4 with Downloader

use of de.mkrtchyan.utils.Downloader in project Rashr by DsLNeXuS.

the class FlashFragment method FlashSupportedKernel.

/**
     * Flash Kernels provided by Rashr like stock kernels for Nexus Devices
     *
     * @param card CardView that contains the Kernel type should be flashed for example: stock,
     *             bricked...
     */
public void FlashSupportedKernel(Card card) {
    final File path;
    final ArrayList<String> Versions;
    ArrayAdapter<String> VersionsAdapter = new ArrayAdapter<>(mContext, R.layout.custom_list_item);
    /**
         * If there files be needed to flash download it and listing device specified recovery
         * file for example stock-boot-grouper-4.4.img (read out from kernel_sums)
         */
    String SYSTEM = card.getData().toString();
    if (SYSTEM.equals(Device.KER_SYS_STOCK)) {
        Versions = RashrApp.DEVICE.getStockKernelVersions();
        path = Const.PathToStockKernel;
        for (String i : Versions) {
            try {
                String version = i.split("-")[3].replace(RashrApp.DEVICE.getRecoveryExt(), "");
                String deviceName = i.split("-")[2];
                /** Readable name for user */
                VersionsAdapter.add("Stock Kernel " + version + " (" + deviceName + ")");
            } catch (ArrayIndexOutOfBoundsException e) {
                /** Add the normal filename if something went wrong */
                VersionsAdapter.add(i);
            }
        }
    } else {
        /** Only stock kernel is supported at the moment, why are you here?
             * Something went wrong better return :)
             */
        return;
    }
    final AlertDialog.Builder KernelDialog = new AlertDialog.Builder(mContext);
    KernelDialog.setTitle(SYSTEM);
    KernelDialog.setAdapter(VersionsAdapter, new DialogInterface.OnClickListener() {

        @Override
        public void onClick(DialogInterface dialog, int which) {
            final File kernel = new File(path, Versions.get(which));
            if (!kernel.exists()) {
                try {
                    URL url = new URL(Const.KERNEL_URL + "/" + kernel.getName());
                    Downloader downloader = new Downloader(url, kernel);
                    final DownloadDialog KernelDownloader = new DownloadDialog(mContext, downloader);
                    KernelDownloader.setOnDownloadListener(new DownloadDialog.OnDownloadListener() {

                        @Override
                        public void onSuccess(File file) {
                            flashKernel(file);
                        }

                        @Override
                        public void onFail(Exception e) {
                            KernelDownloader.retry();
                        }
                    });
                    KernelDownloader.setAskBeforeDownload(true);
                    downloader.setChecksumFile(Const.KernelCollectionFile);
                    KernelDownloader.ask();
                } catch (MalformedURLException ignored) {
                }
            } else {
                flashKernel(kernel);
            }
        }
    });
    KernelDialog.show();
//}
}
Also used : AlertDialog(android.support.v7.app.AlertDialog) MalformedURLException(java.net.MalformedURLException) DialogInterface(android.content.DialogInterface) Downloader(de.mkrtchyan.utils.Downloader) DownloadDialog(de.mkrtchyan.utils.DownloadDialog) SuppressLint(android.annotation.SuppressLint) URL(java.net.URL) MalformedURLException(java.net.MalformedURLException) IOException(java.io.IOException) File(java.io.File) ArrayAdapter(android.widget.ArrayAdapter)

Example 5 with Downloader

use of de.mkrtchyan.utils.Downloader in project Rashr by DsLNeXuS.

the class FlashFragment method catchUpdates.

/**
     * Checking if there are new Kernel and Recovery images to download.
     * Download new list of recoveries and kernels if user want and reload interface.
     * The lists are placed in  dslnexus.de/Android/recovery_links
     * dslnexus.de/Android/kernel_sums
     */
public void catchUpdates() {
    if (mSwipeUpdater != null)
        mSwipeUpdater.setRefreshing(true);
    final Thread updateThread = new Thread(new Runnable() {

        @Override
        public void run() {
            try {
                /** Check changes on server */
                final URL recoveryUrl = new URL(Const.RECOVERY_SUMS_URL);
                URLConnection recoveryCon = recoveryUrl.openConnection();
                //returns size of file on server
                long recoveryListSize = recoveryCon.getContentLength();
                //returns size of local file
                long recoveryListLocalSize = Const.RecoveryCollectionFile.length();
                if (recoveryListSize > 0) {
                    isRecoveryListUpToDate = recoveryListLocalSize == recoveryListSize;
                }
                final URL kernelUrl = new URL(Const.KERNEL_SUMS_URL);
                URLConnection kernelCon = kernelUrl.openConnection();
                long kernelListSize = kernelCon.getContentLength();
                long kernelListLocalSize = Const.KernelCollectionFile.length();
                if (kernelListSize > 0) {
                    isKernelListUpToDate = kernelListLocalSize == kernelListSize;
                }
            } catch (IOException e) {
                mActivity.runOnUiThread(new Runnable() {

                    @Override
                    public void run() {
                        Toast.makeText(mContext, R.string.check_connection, Toast.LENGTH_SHORT).show();
                    }
                });
                if (e.toString() != null) {
                    RashrApp.ERRORS.add(Const.RASHR_TAG + " Error while checking updates: " + e);
                } else {
                    RashrApp.ERRORS.add(Const.RASHR_TAG + " Error while checking updates");
                }
            }
            mActivity.runOnUiThread(new Runnable() {

                @Override
                public void run() {
                    /** Something on server changed */
                    if (!isRecoveryListUpToDate || !isKernelListUpToDate) {
                        /** Counting current images */
                        final int img_count = RashrApp.DEVICE.getStockRecoveryVersions().size() + RashrApp.DEVICE.getCwmRecoveryVersions().size() + RashrApp.DEVICE.getTwrpRecoveryVersions().size() + RashrApp.DEVICE.getPhilzRecoveryVersions().size() + RashrApp.DEVICE.getStockKernelVersions().size() + RashrApp.DEVICE.getCmRecoveriyVersions().size();
                        final URL recoveryURL;
                        final URL kernelURL;
                        try {
                            recoveryURL = new URL(Const.RECOVERY_SUMS_URL);
                            kernelURL = new URL(Const.KERNEL_SUMS_URL);
                        } catch (MalformedURLException e) {
                            RashrApp.ERRORS.add(e.toString());
                            return;
                        }
                        /** Download the new lists */
                        final Downloader rDownloader = new Downloader(recoveryURL, Const.RecoveryCollectionFile);
                        rDownloader.setOverrideFile(true);
                        rDownloader.setOnDownloadListener(new Downloader.OnDownloadListener() {

                            @Override
                            public void onSuccess(File file) {
                                RashrApp.DEVICE.loadRecoveryList();
                                isRecoveryListUpToDate = true;
                                final Downloader kDownloader = new Downloader(kernelURL, Const.KernelCollectionFile);
                                kDownloader.setOverrideFile(true);
                                kDownloader.setOnDownloadListener(new Downloader.OnDownloadListener() {

                                    @Override
                                    public void onSuccess(File file) {
                                        RashrApp.DEVICE.loadKernelList();
                                        isKernelListUpToDate = true;
                                        /** Counting added images (after update) */
                                        final int new_img_count = (RashrApp.DEVICE.getStockRecoveryVersions().size() + RashrApp.DEVICE.getCwmRecoveryVersions().size() + RashrApp.DEVICE.getTwrpRecoveryVersions().size() + RashrApp.DEVICE.getPhilzRecoveryVersions().size() + RashrApp.DEVICE.getStockKernelVersions().size()) + RashrApp.DEVICE.getCmRecoveriyVersions().size() - img_count;
                                        mActivity.runOnUiThread(new Runnable() {

                                            @Override
                                            public void run() {
                                                if (isAdded()) {
                                                    Toast.makeText(mActivity, String.format(getString(R.string.new_imgs_loaded), String.valueOf(new_img_count)), Toast.LENGTH_SHORT).show();
                                                }
                                                if (mSwipeUpdater != null) {
                                                    mSwipeUpdater.setRefreshing(false);
                                                }
                                            }
                                        });
                                    }

                                    @Override
                                    public void onFail(final Exception e) {
                                        String msg;
                                        if (e != null) {
                                            msg = e.getMessage();
                                        } else {
                                            msg = "Error occurred while loading new Kernel Lists";
                                        }
                                        Toast.makeText(mActivity, msg, Toast.LENGTH_SHORT).show();
                                        if (mSwipeUpdater != null) {
                                            mSwipeUpdater.setRefreshing(false);
                                        }
                                    }
                                });
                                kDownloader.download();
                            }

                            @Override
                            public void onFail(final Exception e) {
                                String msg;
                                if (e != null) {
                                    msg = e.getMessage();
                                } else {
                                    msg = "Error occurred while loading new Recovery Lists";
                                }
                                Toast.makeText(mActivity, msg, Toast.LENGTH_SHORT).show();
                                if (mSwipeUpdater != null) {
                                    mSwipeUpdater.setRefreshing(false);
                                }
                            }
                        });
                        rDownloader.download();
                    } else {
                        /** Lists are up to date */
                        mActivity.runOnUiThread(new Runnable() {

                            @Override
                            public void run() {
                                if (!Common.getBooleanPref(mContext, Const.PREF_NAME, Const.PREF_KEY_HIDE_UPDATE_HINTS)) {
                                    Toast.makeText(mContext, R.string.uptodate, Toast.LENGTH_SHORT).show();
                                }
                                if (mSwipeUpdater != null) {
                                    mSwipeUpdater.setRefreshing(false);
                                }
                            }
                        });
                    }
                }
            });
        }
    });
    updateThread.start();
}
Also used : MalformedURLException(java.net.MalformedURLException) Downloader(de.mkrtchyan.utils.Downloader) IOException(java.io.IOException) URL(java.net.URL) URLConnection(java.net.URLConnection) SuppressLint(android.annotation.SuppressLint) MalformedURLException(java.net.MalformedURLException) IOException(java.io.IOException) File(java.io.File)

Aggregations

Downloader (de.mkrtchyan.utils.Downloader)9 File (java.io.File)9 IOException (java.io.IOException)9 MalformedURLException (java.net.MalformedURLException)9 URL (java.net.URL)9 DialogInterface (android.content.DialogInterface)5 AlertDialog (android.support.v7.app.AlertDialog)4 View (android.view.View)4 ArrayAdapter (android.widget.ArrayAdapter)4 DownloadDialog (de.mkrtchyan.utils.DownloadDialog)4 JSONException (org.json.JSONException)4 AppCompatImageView (android.support.v7.widget.AppCompatImageView)3 AppCompatTextView (android.support.v7.widget.AppCompatTextView)3 ScrollView (android.widget.ScrollView)3 BindView (butterknife.BindView)3 SuppressLint (android.annotation.SuppressLint)2 URLConnection (java.net.URLConnection)2 ArrayList (java.util.ArrayList)2 JSONArray (org.json.JSONArray)2 FailedExecuteCommand (org.sufficientlysecure.rootcommands.util.FailedExecuteCommand)2