Android5.0開發範例大全 讀書筆記(六)

来源:http://www.cnblogs.com/xx-wqj/archive/2016/10/23/5988089.html
-Advertisement-
Play Games

(六)與系統交互 6.1後臺通知 1.關於後臺通知,下麵展示6種樣式。值得一提的是,筆者的小米5只能顯示基本樣式,雷軍真是良心廠商啊。 2.首先上佈局xml 3.接著是完整代碼 6.3定時執行周期任務 1.AlarmManager用來管理和執行任務,可以在程式沒有運行的時候執行。並且有多種啟動和計算 ...


(六)與系統交互

6.1後臺通知

1.關於後臺通知,下麵展示6種樣式。值得一提的是,筆者的小米5只能顯示基本樣式,雷軍真是良心廠商啊。

2.首先上佈局xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
>

    <RadioGroup
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:id="@+id/options_group">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Rich Styles"/>
        <RadioButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/option_basic"
            android:text="Basic Notification"
            android:checked="true"/>
        <RadioButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/option_bigtext"
            android:text="BigText Style"
            />
        <RadioButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/option_bigpicture"
            android:text="BigPicture Style"
            />
        <RadioButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/option_inbox"
            android:text="Inbox Style"
            />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:text="Secured Styles"/>
        <RadioButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/option_private"
            android:text="Public Version LockScreen" />
        <RadioButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/option_secret"
            android:text="Secret LockScreen" />
        <RadioButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/option_headsup"
            android:text="Heads-Up Notification" />
    </RadioGroup>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Post a Notification"
        android:onClick="onPostClick"/>
</LinearLayout>

3.接著是完整代碼

ublic class NotificationActivity extends AppCompatActivity {
    private RadioGroup mOptionsGroup;
    private static Handler mHandle = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_notification);
        mOptionsGroup = (RadioGroup) findViewById(R.id.options_group);

    }

    public void onPostClick(View view) {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        final int noteId = mOptionsGroup.getCheckedRadioButtonId();
        Notification note = null;
        switch (noteId) {
            case R.id.option_basic:
            case R.id.option_bigtext:
            case R.id.option_bigpicture:
            case R.id.option_inbox:
                note = buildStyledNotification(noteId);
                break;
            case R.id.option_private:
            case R.id.option_secret:
            case R.id.option_headsup:

                note = buildSecuredNotification(noteId);
                break;
        }
        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        manager.notify(noteId, note);
    }

    private Notification buildStyledNotification(int type) {
        Intent launchIntent = new Intent(this, NotificationActivity.class);
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0, launchIntent, 0);

        NotificationCompat.Builder builder = new NotificationCompat.Builder(NotificationActivity.this);
        builder.setSmallIcon(R.mipmap.ic_launcher)
                .setTicker("something happen")
                .setWhen(System.currentTimeMillis())
                .setAutoCancel(true)
                .setDefaults(Notification.DEFAULT_SOUND)
                .setContentTitle("we are finished")
                .setContentText("click here")
                .setContentIntent(contentIntent);

        switch (type) {
            case R.id.option_basic:
                return builder.build();
            case R.id.option_bigtext:
                builder.addAction(android.R.drawable.ic_menu_call, "Call", contentIntent);
                builder.addAction(android.R.drawable.ic_menu_recent_history, "History", contentIntent);

                NotificationCompat.BigTextStyle textStyle = new NotificationCompat.BigTextStyle(builder);
                textStyle.bigText("BigText Mode");
                return textStyle.build();
            case R.id.option_bigpicture:
                builder.addAction(android.R.drawable.ic_menu_compass, "View Location", contentIntent);

                NotificationCompat.BigPictureStyle pictureStyle = new NotificationCompat.BigPictureStyle(builder);
                pictureStyle.bigPicture(BitmapFactory.decodeResource(getResources(), R.mipmap.cat));
                return pictureStyle.build();
            case R.id.option_inbox:
                NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle(builder);
                inboxStyle.setSummaryText("4 new tasks")
                        .addLine("make dinner")
                        .addLine("call mom")
                        .addLine("call handsome")
                        .addLine("call father");
                return inboxStyle.build();
            default:
                throw new IllegalArgumentException("Unknown Type");
        }
    }

    private Notification buildSecuredNotification(int type) {
        Intent launchIntent = new Intent(this, NotificationActivity.class);
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0, launchIntent, 0);

        NotificationCompat.Builder builder = new NotificationCompat.Builder(NotificationActivity.this);
        builder.setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle("account balance update")
                .setContentText("your account balance is -250")
                .setStyle(new NotificationCompat.BigTextStyle().bigText("Your account balance is-250 please pay"))
                .setContentIntent(contentIntent);


        switch (type) {
            case R.id.option_private:
                Notification publicNote = new Notification.Builder(this)
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .setContentTitle("Account Notification")
                        .setContentText("an important message")
                        .setContentIntent(contentIntent)
                        .build();
                return builder.setPublicVersion(publicNote).build();

            case R.id.option_secret:
                return builder.setVisibility(Notification.VISIBILITY_SECRET).build();
            case R.id.option_headsup:
                return builder.setDefaults(Notification.DEFAULT_SOUND)
                        .setPriority(Notification.PRIORITY_HIGH)
                        .build();
            default:
                throw new IllegalArgumentException("Unknown Type");
        }
    }
}

 6.3定時執行周期任務

1.AlarmManager用來管理和執行任務,可以在程式沒有運行的時候執行。並且有多種啟動和計算時間的方式

  定義一個廣播接收者,在其中附加需要執行的操作

public class AlarmReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Calendar now=Calendar.getInstance();
        DateFormat format= SimpleDateFormat.getTimeInstance();
        Toast.makeText(context,format.format(now.getTime()),Toast.LENGTH_SHORT).show();
    }

}

  通過AlarmManager設置時間任務

public void start(View view) {
        Toast.makeText(this,"Scheduled",Toast.LENGTH_SHORT).show();
        manager.setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime()+ interval, interval,mAlarmIntent);

    }

  為了方便,下麵寫出全部代碼

  其中start是間隔一段時間執行任務

  clock是在指定時間執行任務

public class AlarmActivity extends AppCompatActivity {
private PendingIntent mAlarmIntent;
    private AlarmManager manager;
    private long interval;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_timing);
        Intent launchIntent=new Intent(this,AlarmReceiver.class);
        mAlarmIntent=PendingIntent.getBroadcast(this,0,launchIntent,0);
        manager = (AlarmManager) getSystemService(ALARM_SERVICE);
        interval = 5*1000;
    }

    public void start(View view) {
        Toast.makeText(this,"Scheduled",Toast.LENGTH_SHORT).show();
        manager.setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime()+ interval, interval,mAlarmIntent);

    }

    public void stop(View view) {
        Toast.makeText(this,"Canceled",Toast.LENGTH_SHORT).show();
        manager.cancel(mAlarmIntent);
    }

    public void clock(View view) {
        long oneDay=24*3600*1000;
        long firstTime;
        Calendar startTime=Calendar.getInstance();
        startTime.set(Calendar.HOUR_OF_DAY,17);
        startTime.set(Calendar.MINUTE,18);
        startTime.set(Calendar.SECOND,0);
        Calendar now=Calendar.getInstance();
        if(now.before(startTime)){
            firstTime=startTime.getTimeInMillis();
            System.out.println(firstTime);
        }else {
            startTime.add(Calendar.DATE,1);
            firstTime=startTime.getTimeInMillis();
        }
        manager.setRepeating(AlarmManager.RTC_WAKEUP,firstTime,oneDay,mAlarmIntent);

    }
}

2.JobScheduler也可以實現類似的功能

   1.首先自定義一個service繼承自JobService

public class WorkService extends JobService

  在內部放一個handler來進行簡單的隊列處理

   private Handler mJobProcessor=new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            JobParameters parameters= (JobParameters) msg.obj;
            System.out.println(parameters.getJobId());
            doWork();
            jobFinished(parameters,false);
        return true;
        }
    });

  實現onStartJob()方法,

 @Override
    public boolean onStartJob(JobParameters params) {
        mJobProcessor.sendMessageDelayed(Message.obtain(mJobProcessor,MSG_JOB,params),7500);
        return true;
    }

  實現onStopJob()方法

 @Override
    public boolean onStopJob(JobParameters params) {
        mJobProcessor.removeMessages(MSG_JOB);
        return false;
    }

  實現具體處理任務的doWork()方法

 private void doWork() {
        Calendar now=Calendar.getInstance();
        DateFormat formatter= SimpleDateFormat.getTimeInstance();
        Toast.makeText(this,formatter.format(now.getTime()),Toast.LENGTH_SHORT).show();
    }

  2.在主函數中調用JobScheduler

public class JobSchedulerActivity extends AppCompatActivity {
    private static final int JOB_ID = 1;
    private JobScheduler scheduler;
    private JobInfo info;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_job_scheduler);
        scheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
        long interval =  5 * 1000;
        info = new JobInfo.Builder(JOB_ID, new ComponentName(getPackageName(), WorkService.class.getName()))
                .setPeriodic(interval)
                .build();
    }

    public void start(View view) {

        int result = scheduler.schedule(info);
        if (result <= 0) {
            Toast.makeText(this, "Error", Toast.LENGTH_SHORT).show();
        }
    }

    public void stop(View view) {
        scheduler.cancel(JOB_ID);
    }
}

6.4創建粘性操作

1.粘性操作是指當應用程式被用戶終止時也可以執行的一個或多個操作.

2.IntentService會將要執行的任務放到隊列中,然後逐個請求,全部處理完後終結自己

   以下是自定義IntentService的完整代碼

public class OperationsManager extends IntentService {
    public static final String ACTION_EVENT = "ACTION_EVENT";
    public static final String ACTION_WARNING = "ACTION_WARNING";
    public static final String ACTION_ERROR = "ACTION_ERROR";
    public static final String EXTRA_NAME = "eventName";

    private static final String LOGTAG = "com.joshua.log";
    private IntentFilter matcher;

    public OperationsManager() {
        super("OperationsManager");
        matcher = new IntentFilter();
        matcher.addAction(ACTION_EVENT);
        matcher.addAction(ACTION_WARNING);
        matcher.addAction(ACTION_ERROR);
    }


    @Override
    protected void onHandleIntent(Intent intent) {
        if (!matcher.matchAction(intent.getAction())) {
            Toast.makeText(this, "Invalid Request", Toast.LENGTH_SHORT).show();
        }
        switch (intent.getAction()) {
            case ACTION_EVENT:
                logEvent(intent.getStringExtra(EXTRA_NAME));
                break;
            case ACTION_WARNING:
                logWarning(intent.getStringExtra(EXTRA_NAME));
                break;
            case ACTION_ERROR:
                logError(intent.getStringExtra(EXTRA_NAME));
                break;
        }

    }

    private void logError(String name) {
        try {
            Thread.sleep(5000);
            Log.i(LOGTAG,name);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    private void logWarning(String name) {
        try {
            Thread.sleep(5000);
            Log.w(LOGTAG,name);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void logEvent(String name) {
        try {
            Thread.sleep(5000);
            Log.e(LOGTAG,name);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2.在activity中通過Intent調用IntentService

public class ReportActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_report);
        logEvent("CREATE");
    }

    @Override
    protected void onStart() {
        super.onStart();
        logEvent("START");
    }

    @Override
    protected void onResume() {
        super.onResume();
        logEvent("RESUME");
    }

    @Override
    protected void onPause() {
        super.onPause();
        logEvent("PAUSE");
    }

    @Override
    protected void onStop() {
        super.onStop();
        logEvent("STOP");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        logEvent("DESTROY");
    }

    private void logEvent(String event){
        Intent intent=new Intent(this,OperationsManager.class);
        intent.setAction(OperationsManager.ACTION_EVENT);
        intent.putExtra(OperationsManager.EXTRA_NAME,event);
        startService(intent);
    }

    private void logWarning(String event){
        Intent intent=new Intent(this,OperationsManager.class);
        intent.setAction(OperationsManager.ACTION_WARNING);
        intent.putExtra(OperationsManager.EXTRA_NAME,event);
        startService(intent);
    }
}

6.6分享內容

1.通過Intent啟動系統的其他相關程式

 public void share(View view) {
        Intent intent=new Intent(Intent.ACTION_SEND);
        intent.setType("text/plain");
        intent.putExtra(Intent.EXTRA_TEXT,"分享");
        startActivity(Intent.createChooser(intent,"Share..."));
    }

6.10讀取設備媒體和文檔

1.通過Intent獲取感興趣的設備內容,比如image/video/audio

  不過筆者的小米5不管你調用的是什麼,它都給你彈出文件管理器的首頁……

intent = new Intent();
        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.KITKAT){
            intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
        }else {
            intent.setAction(Intent.ACTION_GET_CONTENT);
        }
        intent.addCategory(Intent.CATEGORY_OPENABLE);

    }
    public void image(View view) {
       intent.setType("image/*");
        startActivityForResult(intent,REQUEST_IMAGE);
    }

    public void video(View view) {
        intent.setType("video/*");
        startActivityForResult(intent,REQUEST_VIDEO);
    }

    public void audio(View view) {
        intent.setType("audio/*");
        startActivityForResult(intent,REQUEST_AUDIO);
    }

6.16自定義任務棧

1.當程式A的A3界面被程式B的B3界面啟動後,按返回是返回B3還是A2呢?當然是B3啦,因為屬於程式A的任務棧並沒有被創建。

  因此如果想要返回A2,則必須啟動A的任務棧。

2.首先,創建一個根界面

public class RootActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_root);
    }

    public void next(View view) {
        Intent intent=new Intent(this,ItemsListActivity.class);
        startActivity(intent);
    }
}

3.接著,創建一個二級界面,其中重點是NavUtils的應用,可以創建任務棧

public class ItemsListActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {
    private static final String[] ITEMS = {"Mon", "Dad", "Sister", "Brother", "Cousin"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_items_list);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        ListView list = new ListView(this);
        ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, ITEMS);
        list.setAdapter(adapter);
        list.setOnItemClickListener(this);
        setContentView(list);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                Intent upIntent = NavUtils.getParentActivityIntent(this);
                if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
                    TaskStackBuilder.create(this)
                            .addParentStack(this)
                            .startActivities();
                } else {
                    NavUtils.navigateUpFromSameTask(this);
                }
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        Intent intent = new Intent(this, DetailsActivity.class);
        intent.putExtra(Intent.EXTRA_TEXT, ITEMS[position]);
        startActivity(intent);

    }
}

4.創建三級界面

public class DetailsActivity extends AppCompatActivity {
private static final String ACTION_NEW_ARRIVAL="com.examples.taskstack.ACTION_NEW_ARRIVAL";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        TextView textView=new TextView(this);
        textView.setGravity(Gravity.CENTER);
        String item=getIntent().getStringExtra(Intent.EXTRA_TEXT);
        textView.setText(item);
        setContentView(textView);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                Intent upIntent = NavUtils.getParentActivityIntent(this);
                if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
                    TaskStackBuilder.create(this)
                            .addParentStack(this)
                            .startActivities();
                } else {
                    NavUtils.navigateUpFromSameTask(this);
                }
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
}

5.AndroidManifest中配置如下,為各級設置parent,其中4.1以後不需要使用meta-data

 <activity android:name=".day20161019.RootActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".day20161019.ItemsListActivity"
            android:parentActivityName=".day20161019.RootActivity">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".day20161019.RootActivity" />
        </activity>
        <activity
            android:name=".day20161019.DetailsActivity"
            android:parentActivityName=".day20161019.ItemsListActivity">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".day20161019.ItemsListActivity" />

            <intent-filter>
                <action android:name="com.examples.taskstack.ACTION_NEW_ARRIVAL" />

                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

(七)總結

看過一個大神的博客,他說自己不覺得有多麼聰明,但別人總愛稱他是大神,可能只是因為自己在半年內看了50多本書吧,至於書該怎麼看,很簡單,敲一遍。

強,無敵。

於是閱讀《Android5.0開發範例大全》的時候,我也把部分代碼敲了一遍,很多時候怎麼看都看不懂的代碼,運行一下大多就能理解了。

不過因為個人水平太差,這文章寫出來估計沒幾個人能讀明白,實在汗顏。

接下來,我會開很多新書,繼續寫讀書筆記這個系列。

希望能多多進步吧。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...